mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-29 16:22:53 +00:00
1632 lines
71 KiB
Python
1632 lines
71 KiB
Python
# BlenderBIM Add-on - OpenBIM Blender Add-on
|
|
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>, 2022 Yassine Oualid <yassine@sigmadimensions.com>
|
|
#
|
|
# This file is part of BlenderBIM Add-on.
|
|
#
|
|
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# BlenderBIM Add-on is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
import bpy
|
|
import re
|
|
import os
|
|
import ifcopenshell
|
|
import ifcopenshell.util.sequence
|
|
import ifcopenshell.util.date
|
|
import ifcopenshell.util.element
|
|
import ifcopenshell.util.unit
|
|
import json
|
|
import blenderbim.core.tool
|
|
import blenderbim.core
|
|
import blenderbim.tool as tool
|
|
import blenderbim.bim.helper
|
|
import blenderbim.bim.module.sequence.helper as helper
|
|
from dateutil import parser
|
|
from datetime import datetime
|
|
import mathutils
|
|
import pystache
|
|
import webbrowser
|
|
|
|
|
|
class Sequence(blenderbim.core.tool.Sequence):
|
|
@classmethod
|
|
def get_work_plan_attributes(cls):
|
|
def callback(attributes, prop):
|
|
if "Date" in prop.name or "Time" in prop.name:
|
|
if prop.is_null:
|
|
attributes[prop.name] = None
|
|
return True
|
|
attributes[prop.name] = helper.parse_datetime(prop.string_value)
|
|
return True
|
|
elif prop.name == "Duration" or prop.name == "TotalFloat":
|
|
if prop.is_null:
|
|
attributes[prop.name] = None
|
|
return True
|
|
attributes[prop.name] = helper.parse_duration(prop.string_value)
|
|
return True
|
|
|
|
props = bpy.context.scene.BIMWorkPlanProperties
|
|
return blenderbim.bim.helper.export_attributes(props.work_plan_attributes, callback)
|
|
|
|
@classmethod
|
|
def load_work_plan_attributes(cls, work_plan):
|
|
def callback(name, prop, data):
|
|
if name in ["CreationDate", "StartTime", "FinishTime"]:
|
|
prop.string_value = "" if prop.is_null else data[name]
|
|
return True
|
|
|
|
props = bpy.context.scene.BIMWorkPlanProperties
|
|
props.work_plan_attributes.clear()
|
|
blenderbim.bim.helper.import_attributes2(work_plan, props.work_plan_attributes, callback)
|
|
|
|
@classmethod
|
|
def enable_editing_work_plan(cls, work_plan):
|
|
if work_plan:
|
|
bpy.context.scene.BIMWorkPlanProperties.active_work_plan_id = work_plan.id()
|
|
bpy.context.scene.BIMWorkPlanProperties.editing_type = "ATTRIBUTES"
|
|
|
|
@classmethod
|
|
def disable_editing_work_plan(cls):
|
|
bpy.context.scene.BIMWorkPlanProperties.active_work_plan_id = 0
|
|
|
|
@classmethod
|
|
def enable_editing_work_plan_schedules(cls, work_plan):
|
|
if work_plan:
|
|
bpy.context.scene.BIMWorkPlanProperties.active_work_plan_id = work_plan.id()
|
|
bpy.context.scene.BIMWorkPlanProperties.editing_type = "SCHEDULES"
|
|
|
|
@classmethod
|
|
def get_work_schedule_attributes(cls):
|
|
def callback(attributes, prop):
|
|
if "Date" in prop.name or "Time" in prop.name:
|
|
if prop.is_null:
|
|
attributes[prop.name] = None
|
|
return True
|
|
attributes[prop.name] = helper.parse_datetime(prop.string_value)
|
|
return True
|
|
elif prop.name == "Duration" or prop.name == "TotalFloat":
|
|
if prop.is_null:
|
|
attributes[prop.name] = None
|
|
return True
|
|
attributes[prop.name] = helper.parse_duration(prop.string_value)
|
|
return True
|
|
|
|
props = bpy.context.scene.BIMWorkScheduleProperties
|
|
return blenderbim.bim.helper.export_attributes(props.work_schedule_attributes, callback)
|
|
|
|
@classmethod
|
|
def load_work_schedule_attributes(cls, work_schedule):
|
|
def callback(name, prop, data):
|
|
if name in ["CreationDate", "StartTime", "FinishTime"]:
|
|
prop.string_value = "" if prop.is_null else data[name]
|
|
return True
|
|
|
|
props = bpy.context.scene.BIMWorkScheduleProperties
|
|
props.work_schedule_attributes.clear()
|
|
blenderbim.bim.helper.import_attributes2(work_schedule, props.work_schedule_attributes, callback)
|
|
|
|
@classmethod
|
|
def enable_editing_work_schedule(cls, work_schedule):
|
|
bpy.context.scene.BIMWorkScheduleProperties.active_work_schedule_id = work_schedule.id()
|
|
bpy.context.scene.BIMWorkScheduleProperties.editing_type = "WORK_SCHEDULE"
|
|
|
|
@classmethod
|
|
def disable_editing_work_schedule(cls):
|
|
bpy.context.scene.BIMWorkScheduleProperties.active_work_schedule_id = 0
|
|
|
|
@classmethod
|
|
def enable_editing_work_schedule_tasks(cls, work_schedule):
|
|
if work_schedule:
|
|
props = bpy.context.scene.BIMWorkScheduleProperties
|
|
props.active_work_schedule_id = work_schedule.id()
|
|
props.editing_type = "TASKS"
|
|
|
|
@classmethod
|
|
def load_task_tree(cls, work_schedule):
|
|
bpy.context.scene.BIMTaskTreeProperties.tasks.clear()
|
|
props = bpy.context.scene.BIMWorkScheduleProperties
|
|
cls.contracted_tasks = json.loads(props.contracted_tasks)
|
|
|
|
related_objects_ids = cls.get_sorted_tasks_ids(ifcopenshell.util.sequence.get_root_tasks(work_schedule))
|
|
for related_object_id in related_objects_ids:
|
|
cls.create_new_task_li(related_object_id, 0)
|
|
|
|
@classmethod
|
|
def get_sorted_tasks_ids(cls, tasks):
|
|
def get_sort_key(task):
|
|
# Sorting only applies to actual tasks, not the WBS
|
|
# for rel in task.IsNestedBy:
|
|
# for object in rel.RelatedObjects:
|
|
# if object.is_a("IfcTask"):
|
|
# return "0000000000" + (task.Identification or "")
|
|
column_type, name = bpy.context.scene.BIMWorkScheduleProperties.sort_column.split(".")
|
|
if column_type == "IfcTask":
|
|
return task.get_info(task)[name] or ""
|
|
elif column_type == "IfcTaskTime" and task.TaskTime:
|
|
return task.TaskTime.get_info(task)[name] if task.TaskTime.get_info(task)[name] else ""
|
|
return task.Identification or ""
|
|
|
|
def natural_sort_key(i, _nsre=re.compile("([0-9]+)")):
|
|
s = sort_keys[i]
|
|
return [int(text) if text.isdigit() else text.lower() for text in _nsre.split(s)]
|
|
|
|
if bpy.context.scene.BIMWorkScheduleProperties.sort_column:
|
|
sort_keys = {task.id(): get_sort_key(task) for task in tasks}
|
|
related_object_ids = sorted(sort_keys, key=natural_sort_key)
|
|
else:
|
|
related_object_ids = [task.id() for task in tasks]
|
|
if bpy.context.scene.BIMWorkScheduleProperties.is_sort_reversed:
|
|
related_object_ids.reverse()
|
|
return related_object_ids
|
|
|
|
@classmethod
|
|
def create_new_task_li(cls, related_object_id, level_index):
|
|
task = tool.Ifc.get().by_id(related_object_id)
|
|
new = bpy.context.scene.BIMTaskTreeProperties.tasks.add()
|
|
new.ifc_definition_id = related_object_id
|
|
new.is_expanded = related_object_id not in cls.contracted_tasks
|
|
new.level_index = level_index
|
|
if task.IsNestedBy:
|
|
new.has_children = True
|
|
if new.is_expanded:
|
|
for related_object_id in cls.get_sorted_tasks_ids(ifcopenshell.util.sequence.get_nested_tasks(task)):
|
|
cls.create_new_task_li(related_object_id, level_index + 1)
|
|
|
|
@classmethod
|
|
def load_task_properties(cls, task=None):
|
|
def canonicalise_time(time):
|
|
if not time:
|
|
return "-"
|
|
return time.strftime("%d/%m/%y")
|
|
|
|
props = bpy.context.scene.BIMWorkScheduleProperties
|
|
task_props = bpy.context.scene.BIMTaskTreeProperties
|
|
props.is_task_update_enabled = False
|
|
|
|
for item in task_props.tasks:
|
|
# if task and item.ifc_definition_id != task.id():
|
|
# continue
|
|
task = tool.Ifc.get().by_id(item.ifc_definition_id)
|
|
item.name = task.Name or "Unnamed"
|
|
item.identification = task.Identification or "XXX"
|
|
if props.active_task_id:
|
|
item.is_predecessor = props.active_task_id in [rel.RelatedProcess.id() for rel in task.IsPredecessorTo]
|
|
item.is_successor = props.active_task_id in [rel.RelatingProcess.id() for rel in task.IsSuccessorFrom]
|
|
calendar = ifcopenshell.util.sequence.derive_calendar(task)
|
|
if task.HasAssignments:
|
|
for rel in task.HasAssignments:
|
|
if rel.is_a("IfcRelAssignsToControl") and rel.RelatingControl.is_a("IfcWorkCalendar"):
|
|
item.calendar = calendar.Name or "Unnamed" if calendar else ""
|
|
else:
|
|
item.calendar = ""
|
|
item.derived_calendar = calendar.Name or "Unnamed" if calendar else ""
|
|
|
|
if task.TaskTime and (
|
|
task.TaskTime.ScheduleStart or task.TaskTime.ScheduleFinish or task.TaskTime.ScheduleDuration
|
|
):
|
|
task_time = task.TaskTime
|
|
item.start = (
|
|
canonicalise_time(ifcopenshell.util.date.ifc2datetime(task_time.ScheduleStart))
|
|
if task_time.ScheduleStart
|
|
else "-"
|
|
)
|
|
item.finish = (
|
|
canonicalise_time(ifcopenshell.util.date.ifc2datetime(task_time.ScheduleFinish))
|
|
if task_time.ScheduleFinish
|
|
else "-"
|
|
)
|
|
item.duration = (
|
|
str(ifcopenshell.util.date.readable_ifc_duration(task_time.ScheduleDuration))
|
|
if task_time.ScheduleDuration
|
|
else "-"
|
|
)
|
|
else:
|
|
derived_start = ifcopenshell.util.sequence.derive_date(task, "ScheduleStart", is_earliest=True)
|
|
derived_finish = ifcopenshell.util.sequence.derive_date(task, "ScheduleFinish", is_latest=True)
|
|
item.derived_start = canonicalise_time(derived_start) if derived_start else ""
|
|
item.derived_finish = canonicalise_time(derived_finish) if derived_finish else ""
|
|
if derived_start and derived_finish and calendar:
|
|
derived_duration = ifcopenshell.util.sequence.count_working_days(
|
|
derived_start, derived_finish, calendar
|
|
)
|
|
item.derived_duration = f"P{derived_duration}D"
|
|
item.start = "-"
|
|
item.finish = "-"
|
|
item.duration = "-"
|
|
|
|
props.is_task_update_enabled = True
|
|
|
|
@classmethod
|
|
def get_active_work_schedule(cls):
|
|
if not bpy.context.scene.BIMWorkScheduleProperties.active_work_schedule_id:
|
|
return None
|
|
return tool.Ifc.get().by_id(bpy.context.scene.BIMWorkScheduleProperties.active_work_schedule_id)
|
|
|
|
@classmethod
|
|
def get_selected_resource(cls):
|
|
if bpy.context.scene.BIMResourceTreeProperties.resources:
|
|
selected_resource_id = bpy.context.scene.BIMResourceTreeProperties.resources[
|
|
bpy.context.scene.BIMResourceProperties.active_resource_index
|
|
].ifc_definition_id
|
|
return tool.Ifc.get().by_id(selected_resource_id)
|
|
|
|
@classmethod
|
|
def expand_task(cls, task):
|
|
props = bpy.context.scene.BIMWorkScheduleProperties
|
|
contracted_tasks = json.loads(props.contracted_tasks)
|
|
contracted_tasks.remove(task.id())
|
|
props.contracted_tasks = json.dumps(contracted_tasks)
|
|
|
|
@classmethod
|
|
def expand_all_tasks(cls):
|
|
bpy.context.scene.BIMWorkScheduleProperties.contracted_tasks = json.dumps([])
|
|
|
|
@classmethod
|
|
def contract_all_tasks(cls):
|
|
props = bpy.context.scene.BIMWorkScheduleProperties
|
|
contracted_tasks = json.loads(props.contracted_tasks)
|
|
for task_item in bpy.context.scene.BIMTaskTreeProperties.tasks:
|
|
if task_item.is_expanded:
|
|
contracted_tasks.append(task_item.ifc_definition_id)
|
|
props.contracted_tasks = json.dumps(contracted_tasks)
|
|
|
|
@classmethod
|
|
def contract_task(cls, task):
|
|
props = bpy.context.scene.BIMWorkScheduleProperties
|
|
contracted_tasks = json.loads(props.contracted_tasks)
|
|
contracted_tasks.append(task.id())
|
|
props.contracted_tasks = json.dumps(contracted_tasks)
|
|
|
|
@classmethod
|
|
def disable_work_schedule(cls):
|
|
bpy.context.scene.BIMWorkScheduleProperties.active_work_schedule_id = 0
|
|
|
|
@classmethod
|
|
def disable_selecting_deleted_task(cls):
|
|
props = bpy.context.scene.BIMWorkScheduleProperties
|
|
if props.active_task_id not in [
|
|
task.ifc_definition_id for task in bpy.context.scene.BIMTaskTreeProperties.tasks
|
|
]: # Task was deleted
|
|
bpy.context.scene.BIMWorkScheduleProperties.active_task_id = 0
|
|
bpy.context.scene.BIMWorkScheduleProperties.active_task_time_id = 0
|
|
|
|
@classmethod
|
|
def get_checked_tasks(cls):
|
|
return [
|
|
tool.Ifc.get().by_id(task.ifc_definition_id)
|
|
for task in bpy.context.scene.BIMTaskTreeProperties.tasks
|
|
if task.is_selected
|
|
] or []
|
|
|
|
@classmethod
|
|
def get_task_attribute_value(cls, attribute_name):
|
|
return bpy.context.scene.BIMWorkScheduleProperties.task_attributes.get(attribute_name).get_value()
|
|
|
|
@classmethod
|
|
def get_active_task(cls):
|
|
return tool.Ifc.get().by_id(bpy.context.scene.BIMWorkScheduleProperties.active_task_id)
|
|
|
|
@classmethod
|
|
def get_active_work_time(cls):
|
|
return tool.Ifc.get().by_id(bpy.context.scene.BIMWorkCalendarProperties.active_work_time_id)
|
|
|
|
@classmethod
|
|
def get_task_time(cls, task):
|
|
return task.TaskTime if task.TaskTime else None
|
|
|
|
@classmethod
|
|
def load_task_attributes(cls, task):
|
|
props = bpy.context.scene.BIMWorkScheduleProperties
|
|
props.task_attributes.clear()
|
|
blenderbim.bim.helper.import_attributes2(task, props.task_attributes)
|
|
|
|
@classmethod
|
|
def enable_editing_task_attributes(cls, task):
|
|
props = bpy.context.scene.BIMWorkScheduleProperties
|
|
props.active_task_id = task.id()
|
|
props.editing_task_type = "ATTRIBUTES"
|
|
|
|
@classmethod
|
|
def get_task_attributes(cls):
|
|
return blenderbim.bim.helper.export_attributes(bpy.context.scene.BIMWorkScheduleProperties.task_attributes)
|
|
|
|
@classmethod
|
|
def load_task_time_attributes(cls, task_time):
|
|
def callback(name, prop, data):
|
|
if prop and prop.data_type == "string":
|
|
duration_props = bpy.context.scene.BIMWorkScheduleProperties.durations_attributes.add()
|
|
duration_props.name = name
|
|
if prop.is_null:
|
|
for key in duration_props.keys():
|
|
if key != "name":
|
|
setattr(duration_props, key, 0)
|
|
return True
|
|
if name in ["ScheduleDuration", "ActualDuration", "FreeFloat", "TotalFloat"] and data[name]:
|
|
for key, value in helper.parse_duration_as_blender_props(data[name]).items():
|
|
duration_props[key] = value
|
|
return True
|
|
if isinstance(data[name], datetime):
|
|
prop.string_value = "" if prop.is_null else data[name].isoformat()
|
|
return True
|
|
|
|
props = bpy.context.scene.BIMWorkScheduleProperties
|
|
props.task_time_attributes.clear()
|
|
props.durations_attributes.clear()
|
|
blenderbim.bim.helper.import_attributes2(task_time, props.task_time_attributes, callback)
|
|
|
|
@classmethod
|
|
def enable_editing_task_time(cls, task):
|
|
props = bpy.context.scene.BIMWorkScheduleProperties
|
|
props.active_task_id = task.id()
|
|
props.active_task_time_id = task.TaskTime.id()
|
|
props.editing_task_type = "TASKTIME"
|
|
|
|
@classmethod
|
|
def disable_editing_task(cls):
|
|
bpy.context.scene.BIMWorkScheduleProperties.active_task_id = 0
|
|
bpy.context.scene.BIMWorkScheduleProperties.active_task_time_id = 0
|
|
|
|
@classmethod
|
|
def get_task_time_attributes(cls):
|
|
def callback(attributes, prop):
|
|
|
|
if "Start" in prop.name or "Finish" in prop.name or prop.name == "StatusTime":
|
|
if prop.is_null:
|
|
attributes[prop.name] = None
|
|
return True
|
|
attributes[prop.name] = helper.parse_datetime(prop.string_value)
|
|
return True
|
|
elif prop.name in ["ScheduleDuration", "ActualDuration", "FreeFloat", "TotalFloat"]:
|
|
props = bpy.context.scene.BIMWorkScheduleProperties
|
|
if prop.is_null:
|
|
attributes[prop.name] = None
|
|
for value in props.durations_attributes.values():
|
|
value = 0
|
|
return True
|
|
else:
|
|
duration_type = attributes["DurationType"] if "DurationType" in attributes else None
|
|
time_split_iso_duration = helper.blender_props_to_iso_duration(
|
|
props.durations_attributes, duration_type, prop.name
|
|
)
|
|
attributes[prop.name] = time_split_iso_duration
|
|
for value in props.durations_attributes.values():
|
|
value = 0
|
|
return True
|
|
|
|
props = bpy.context.scene.BIMWorkScheduleProperties
|
|
return blenderbim.bim.helper.export_attributes(props.task_time_attributes, callback)
|
|
|
|
@classmethod
|
|
def load_task_resources(cls, resources):
|
|
props = bpy.context.scene.BIMWorkScheduleProperties
|
|
props.task_resources.clear()
|
|
for resource in resources or []:
|
|
new = props.task_resources.add()
|
|
new.ifc_definition_id = resource.id()
|
|
new.name = resource.Name or "Unnamed"
|
|
new.schedule_usage = resource.Usage.ScheduleUsage or 1 if resource.Usage else 0
|
|
|
|
@classmethod
|
|
def load_resources(cls):
|
|
blenderbim.core.resource.load_resources(tool.Resource)
|
|
|
|
@classmethod
|
|
def get_task_inputs(cls, task):
|
|
is_deep = bpy.context.scene.BIMWorkScheduleProperties.show_nested_inputs
|
|
return ifcopenshell.util.sequence.get_task_inputs(task, is_deep)
|
|
|
|
@classmethod
|
|
def get_task_outputs(cls, task):
|
|
is_deep = bpy.context.scene.BIMWorkScheduleProperties.show_nested_outputs
|
|
return ifcopenshell.util.sequence.get_task_outputs(task, is_deep)
|
|
|
|
@classmethod
|
|
def are_entities_same_class(cls, entities):
|
|
if not entities:
|
|
return False
|
|
if len(entities) == 1:
|
|
return True
|
|
first = entities[0]
|
|
for entity in entities:
|
|
if entity.is_a() != first.is_a():
|
|
return False
|
|
return True
|
|
|
|
@classmethod
|
|
def get_task_resources(cls, task):
|
|
is_deep = bpy.context.scene.BIMWorkScheduleProperties.show_nested_resources
|
|
return ifcopenshell.util.sequence.get_task_resources(task, is_deep)
|
|
|
|
@classmethod
|
|
def load_task_inputs(cls, inputs):
|
|
props = bpy.context.scene.BIMWorkScheduleProperties
|
|
props.task_inputs.clear()
|
|
for input in inputs:
|
|
new = props.task_inputs.add()
|
|
new.ifc_definition_id = input.id()
|
|
new.name = input.Name or "Unnamed"
|
|
|
|
@classmethod
|
|
def load_task_outputs(cls, outputs):
|
|
props = bpy.context.scene.BIMWorkScheduleProperties
|
|
props.task_outputs.clear()
|
|
if outputs:
|
|
for output in outputs:
|
|
new = props.task_outputs.add()
|
|
new.ifc_definition_id = output.id()
|
|
new.name = output.Name or "Unnamed"
|
|
|
|
@classmethod
|
|
def get_highlighted_task(cls):
|
|
props = bpy.context.scene.BIMWorkScheduleProperties
|
|
task_props = bpy.context.scene.BIMTaskTreeProperties
|
|
return tool.Ifc.get().by_id(task_props.tasks[props.active_task_index].ifc_definition_id)
|
|
|
|
@classmethod
|
|
def get_direct_nested_tasks(cls, task):
|
|
return ifcopenshell.util.sequence.get_nested_tasks(task)
|
|
|
|
@classmethod
|
|
def get_direct_task_outputs(cls, task):
|
|
return ifcopenshell.util.sequence.get_direct_task_outputs(task)
|
|
|
|
@classmethod
|
|
def get_task_outputs(cls, task):
|
|
is_deep = bpy.context.scene.BIMWorkScheduleProperties.show_nested_outputs
|
|
return ifcopenshell.util.sequence.get_task_outputs(task, is_deep)
|
|
|
|
@classmethod
|
|
def get_task_resources(cls, task):
|
|
resources = []
|
|
for rel in task.OperatesOn:
|
|
for object in rel.RelatedObjects:
|
|
if object.is_a("IfcResource"):
|
|
resources.append(object)
|
|
return resources
|
|
|
|
@classmethod
|
|
def enable_editing_work_calendar_times(cls, work_calendar):
|
|
props = bpy.context.scene.BIMWorkCalendarProperties
|
|
props.active_work_calendar_id = work_calendar.id()
|
|
props.editing_type = "WORKTIMES"
|
|
|
|
@classmethod
|
|
def load_work_calendar_attributes(cls, work_calendar):
|
|
props = bpy.context.scene.BIMWorkCalendarProperties
|
|
props.work_calendar_attributes.clear()
|
|
return blenderbim.bim.helper.import_attributes2(work_calendar, props.work_calendar_attributes)
|
|
|
|
@classmethod
|
|
def enable_editing_work_calendar(cls, work_calendar):
|
|
bpy.context.scene.BIMWorkCalendarProperties.active_work_calendar_id = work_calendar.id()
|
|
bpy.context.scene.BIMWorkCalendarProperties.editing_type = "ATTRIBUTES"
|
|
|
|
@classmethod
|
|
def disable_editing_work_calendar(cls):
|
|
bpy.context.scene.BIMWorkCalendarProperties.active_work_calendar_id = 0
|
|
|
|
@classmethod
|
|
def get_work_calendar_attributes(cls):
|
|
return blenderbim.bim.helper.export_attributes(
|
|
bpy.context.scene.BIMWorkCalendarProperties.work_calendar_attributes
|
|
)
|
|
|
|
@classmethod
|
|
def load_work_time_attributes(cls, work_time):
|
|
def callback(name, prop, data):
|
|
if name in ["Start", "Finish"]:
|
|
prop.string_value = "" if prop.is_null else data[name]
|
|
return True
|
|
|
|
props = bpy.context.scene.BIMWorkCalendarProperties
|
|
props.work_time_attributes.clear()
|
|
|
|
blenderbim.bim.helper.import_attributes2(work_time, props.work_time_attributes, callback)
|
|
|
|
@classmethod
|
|
def enable_editing_work_time(cls, work_time):
|
|
def initialise_recurrence_components(props):
|
|
if len(props.day_components) == 0:
|
|
for i in range(0, 31):
|
|
new = props.day_components.add()
|
|
new.name = str(i + 1)
|
|
if len(props.weekday_components) == 0:
|
|
for d in ["M", "T", "W", "T", "F", "S", "S"]:
|
|
new = props.weekday_components.add()
|
|
new.name = d
|
|
if len(props.month_components) == 0:
|
|
for m in ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]:
|
|
new = props.month_components.add()
|
|
new.name = m
|
|
|
|
def load_recurrence_pattern_data(work_time, props):
|
|
props.position = 0
|
|
props.interval = 0
|
|
props.occurrences = 0
|
|
props.start_time = ""
|
|
props.end_time = ""
|
|
for component in props.day_components:
|
|
component.is_specified = False
|
|
for component in props.weekday_components:
|
|
component.is_specified = False
|
|
for component in props.month_components:
|
|
component.is_specified = False
|
|
if not work_time.RecurrencePattern:
|
|
return
|
|
recurrence_pattern = work_time.RecurrencePattern
|
|
for attribute in ["Position", "Interval", "Occurrences"]:
|
|
if getattr(recurrence_pattern, attribute):
|
|
setattr(props, attribute.lower(), getattr(recurrence_pattern, attribute))
|
|
for component in recurrence_pattern.DayComponent or []:
|
|
props.day_components[component - 1].is_specified = True
|
|
for component in recurrence_pattern.WeekdayComponent or []:
|
|
props.weekday_components[component - 1].is_specified = True
|
|
for component in recurrence_pattern.MonthComponent or []:
|
|
props.month_components[component - 1].is_specified = True
|
|
|
|
props = bpy.context.scene.BIMWorkCalendarProperties
|
|
initialise_recurrence_components(props)
|
|
load_recurrence_pattern_data(work_time, props)
|
|
props.active_work_time_id = work_time.id()
|
|
props.editing_type = "WORKTIMES"
|
|
|
|
@classmethod
|
|
def get_work_time_attributes(cls):
|
|
def callback(attributes, prop):
|
|
if "Start" in prop.name or "Finish" in prop.name:
|
|
if prop.is_null:
|
|
attributes[prop.name] = None
|
|
return True
|
|
attributes[prop.name] = helper.parse_datetime(prop.string_value)
|
|
return True
|
|
|
|
props = bpy.context.scene.BIMWorkCalendarProperties
|
|
return blenderbim.bim.helper.export_attributes(props.work_time_attributes, callback)
|
|
|
|
@classmethod
|
|
def get_recurrence_pattern_attributes(cls, recurrence_pattern):
|
|
props = bpy.context.scene.BIMWorkCalendarProperties
|
|
attributes = {
|
|
"Interval": props.interval if props.interval > 0 else None,
|
|
"Occurrences": props.occurrences if props.occurrences > 0 else None,
|
|
}
|
|
applicable_data = {
|
|
"DAILY": ["Interval", "Occurrences"],
|
|
"WEEKLY": ["WeekdayComponent", "Interval", "Occurrences"],
|
|
"MONTHLY_BY_DAY_OF_MONTH": ["DayComponent", "Interval", "Occurrences"],
|
|
"MONTHLY_BY_POSITION": ["WeekdayComponent", "Position", "Interval", "Occurrences"],
|
|
"BY_DAY_COUNT": ["Interval", "Occurrences"],
|
|
"BY_WEEKDAY_COUNT": ["WeekdayComponent", "Interval", "Occurrences"],
|
|
"YEARLY_BY_DAY_OF_MONTH": ["DayComponent", "MonthComponent", "Interval", "Occurrences"],
|
|
"YEARLY_BY_POSITION": ["WeekdayComponent", "MonthComponent", "Position", "Interval", "Occurrences"],
|
|
}
|
|
if "Position" in applicable_data[recurrence_pattern.RecurrenceType]:
|
|
attributes["Position"] = props.position if props.position != 0 else None
|
|
if "DayComponent" in applicable_data[recurrence_pattern.RecurrenceType]:
|
|
attributes["DayComponent"] = [i + 1 for i, c in enumerate(props.day_components) if c.is_specified]
|
|
if "WeekdayComponent" in applicable_data[recurrence_pattern.RecurrenceType]:
|
|
attributes["WeekdayComponent"] = [i + 1 for i, c in enumerate(props.weekday_components) if c.is_specified]
|
|
if "MonthComponent" in applicable_data[recurrence_pattern.RecurrenceType]:
|
|
attributes["MonthComponent"] = [i + 1 for i, c in enumerate(props.month_components) if c.is_specified]
|
|
return attributes
|
|
|
|
@classmethod
|
|
def disable_editing_work_time(cls):
|
|
bpy.context.scene.BIMWorkCalendarProperties.active_work_time_id = 0
|
|
|
|
@classmethod
|
|
def get_recurrence_pattern_times(cls):
|
|
props = bpy.context.scene.BIMWorkCalendarProperties
|
|
try:
|
|
start_time = parser.parse(props.start_time)
|
|
end_time = parser.parse(props.end_time)
|
|
return start_time, end_time
|
|
except:
|
|
return # improve UI / refactor to add user hints
|
|
|
|
@classmethod
|
|
def reset_time_period(cls):
|
|
bpy.context.scene.BIMWorkCalendarProperties.start_time = ""
|
|
bpy.context.scene.BIMWorkCalendarProperties.end_time = ""
|
|
|
|
@classmethod
|
|
def enable_editing_task_calendar(cls, task):
|
|
props = bpy.context.scene.BIMWorkScheduleProperties
|
|
props.active_task_id = task.id()
|
|
props.editing_task_type = "CALENDAR"
|
|
|
|
@classmethod
|
|
def enable_editing_task_sequence(cls, task):
|
|
props = bpy.context.scene.BIMWorkScheduleProperties
|
|
props.active_task_id = task.id()
|
|
props.editing_task_type = "SEQUENCE"
|
|
|
|
@classmethod
|
|
def disable_editing_task_time(cls):
|
|
bpy.context.scene.BIMWorkScheduleProperties.active_task_id = 0
|
|
bpy.context.scene.BIMWorkScheduleProperties.active_task_time_id = 0
|
|
|
|
@classmethod
|
|
def load_rel_sequence_attributes(cls, rel_sequence):
|
|
props = bpy.context.scene.BIMWorkScheduleProperties
|
|
props.sequence_attributes.clear()
|
|
blenderbim.bim.helper.import_attributes2(rel_sequence, props.sequence_attributes)
|
|
|
|
@classmethod
|
|
def enable_editing_rel_sequence_attributes(cls, rel_sequence):
|
|
props = bpy.context.scene.BIMWorkScheduleProperties
|
|
props.active_sequence_id = rel_sequence.id()
|
|
props.editing_sequence_type = "ATTRIBUTES"
|
|
|
|
@classmethod
|
|
def load_lag_time_attributes(cls, lag_time):
|
|
def callback(name, prop, data):
|
|
if name == "LagValue":
|
|
prop = bpy.context.scene.BIMWorkScheduleProperties.lag_time_attributes.add()
|
|
prop.name = name
|
|
prop.is_null = data[name] is None
|
|
prop.is_optional = False
|
|
prop.data_type = "string"
|
|
prop.string_value = (
|
|
"" if prop.is_null else ifcopenshell.util.date.datetime2ifc(data[name].wrappedValue, "IfcDuration")
|
|
)
|
|
return True
|
|
|
|
props = bpy.context.scene.BIMWorkScheduleProperties
|
|
props.lag_time_attributes.clear()
|
|
blenderbim.bim.helper.import_attributes2(lag_time, props.lag_time_attributes, callback)
|
|
|
|
@classmethod
|
|
def enable_editing_sequence_lag_time(cls, rel_sequence):
|
|
props = bpy.context.scene.BIMWorkScheduleProperties
|
|
props.active_sequence_id = rel_sequence.id()
|
|
props.editing_sequence_type = "LAG_TIME"
|
|
|
|
@classmethod
|
|
def get_rel_sequence_attributes(cls):
|
|
return blenderbim.bim.helper.export_attributes(bpy.context.scene.BIMWorkScheduleProperties.sequence_attributes)
|
|
|
|
@classmethod
|
|
def disable_editing_rel_sequence(cls):
|
|
bpy.context.scene.BIMWorkScheduleProperties.active_sequence_id = 0
|
|
|
|
@classmethod
|
|
def get_lag_time_attributes(cls):
|
|
return blenderbim.bim.helper.export_attributes(bpy.context.scene.BIMWorkScheduleProperties.lag_time_attributes)
|
|
|
|
@classmethod
|
|
def select_products(cls, products):
|
|
[obj.select_set(False) for obj in bpy.context.selected_objects]
|
|
for product in products:
|
|
obj = tool.Ifc.get_object(product)
|
|
obj.select_set(True) if obj else None
|
|
|
|
@classmethod
|
|
def add_task_column(cls, column_type, name, data_type):
|
|
props = bpy.context.scene.BIMWorkScheduleProperties
|
|
new = props.columns.add()
|
|
new.name = f"{column_type}.{name}"
|
|
new.data_type = data_type
|
|
|
|
@classmethod
|
|
def setup_default_task_columns(cls):
|
|
props = bpy.context.scene.BIMWorkScheduleProperties
|
|
props.columns.clear()
|
|
default_columns = ["ScheduleStart", "ScheduleFinish", "ScheduleDuration"]
|
|
for item in default_columns:
|
|
new = props.columns.add()
|
|
new.name = f"IfcTaskTime.{item}"
|
|
new.data_type = "string"
|
|
|
|
@classmethod
|
|
def remove_task_column(cls, name):
|
|
props = bpy.context.scene.BIMWorkScheduleProperties
|
|
props.columns.remove(props.columns.find(name))
|
|
if props.sort_column == name:
|
|
props.sort_column = ""
|
|
|
|
@classmethod
|
|
def set_task_sort_column(cls, column):
|
|
props = bpy.context.scene.BIMWorkScheduleProperties
|
|
props.sort_column = column
|
|
|
|
@classmethod
|
|
def find_related_input_tasks(cls, product):
|
|
related_tasks = []
|
|
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, product):
|
|
related_tasks = []
|
|
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
|
|
|
|
@classmethod
|
|
def get_work_schedule(cls, task):
|
|
for rel in task.HasAssignments or []:
|
|
if rel.is_a("IfcRelAssignsToControl") and rel.RelatingControl.is_a("IfcWorkSchedule"):
|
|
return rel.RelatingControl
|
|
for rel in task.Nests or []:
|
|
return cls.get_work_schedule(rel.RelatingObject)
|
|
|
|
@classmethod
|
|
def is_work_schedule_active(cls, work_schedule):
|
|
return (
|
|
True if work_schedule.id() == bpy.context.scene.BIMWorkScheduleProperties.active_work_schedule_id else False
|
|
)
|
|
|
|
@classmethod
|
|
def highlight_task(cls, task):
|
|
def expand_ancestors(task):
|
|
for rel in task.Nests or []:
|
|
parent_task = rel.RelatingObject if rel.RelatingObject.is_a("IfcTask") else None
|
|
contracted_tasks = json.loads(bpy.context.scene.BIMWorkScheduleProperties.contracted_tasks)
|
|
if parent_task and parent_task.id() in contracted_tasks:
|
|
contracted_tasks.remove(parent_task.id())
|
|
bpy.context.scene.BIMWorkScheduleProperties.contracted_tasks = json.dumps(contracted_tasks)
|
|
expand_ancestors(parent_task)
|
|
work_schedule = cls.get_active_work_schedule()
|
|
cls.load_task_tree(work_schedule)
|
|
cls.load_task_properties()
|
|
|
|
task_props = bpy.context.scene.BIMTaskTreeProperties
|
|
displayed_tasks = [item.ifc_definition_id for item in task_props.tasks]
|
|
if not task.id() in displayed_tasks:
|
|
expand_ancestors(task)
|
|
task_index = displayed_tasks.index(task.id()) or 0
|
|
bpy.context.scene.BIMWorkScheduleProperties.active_task_index = task_index
|
|
|
|
@classmethod
|
|
def guess_date_range(cls, work_schedule):
|
|
return ifcopenshell.util.sequence.guess_date_range(work_schedule)
|
|
|
|
@classmethod
|
|
def update_visualisation_date(cls, start_date, finish_date):
|
|
def canonicalise_time(time):
|
|
return time.strftime("%d/%m/%y")
|
|
|
|
if not (start_date and finish_date):
|
|
return
|
|
props = bpy.context.scene.BIMWorkScheduleProperties
|
|
props.visualisation_start = canonicalise_time(start_date)
|
|
props.visualisation_finish = canonicalise_time(finish_date)
|
|
|
|
@classmethod
|
|
def get_animation_bar_tasks(cls):
|
|
return [
|
|
tool.Ifc.get().by_id(item.ifc_definition_id)
|
|
for item in bpy.context.scene.BIMTaskTreeProperties.tasks
|
|
if item.has_bar_visual
|
|
] or []
|
|
|
|
@classmethod
|
|
def create_bars(cls, tasks):
|
|
full_bar_thickness = 0.2
|
|
size = 1.0
|
|
vertical_spacing = 3.5
|
|
vertical_increment = 0
|
|
size_to_duration_ratio = 1 / 30
|
|
margin = 0.2
|
|
|
|
def process_task_data(task, settings):
|
|
task_start_date = ifcopenshell.util.sequence.derive_date(task, "ScheduleStart", is_earliest=True)
|
|
finish_date = ifcopenshell.util.sequence.derive_date(task, "ScheduleFinish", is_latest=True)
|
|
|
|
if not (task_start_date and finish_date):
|
|
return
|
|
else:
|
|
total_frames = settings["end_frame"] - settings["start_frame"]
|
|
duration = settings["viz_finish"] - settings["viz_start"]
|
|
task_start_frame = round(
|
|
settings["start_frame"] + (((task_start_date - settings["viz_start"]) / duration) * total_frames)
|
|
)
|
|
task_finish_frame = round(
|
|
settings["start_frame"] + (((finish_date - settings["viz_start"]) / duration) * total_frames)
|
|
)
|
|
return {
|
|
"name": task.Name if task.Name else "Unnamed",
|
|
"start_date": task_start_date,
|
|
"finish_date": finish_date,
|
|
"start_frame": task_start_frame,
|
|
"finish_frame": task_finish_frame,
|
|
}
|
|
|
|
def create_task_bar_data(tasks, vertical_increment, collection):
|
|
props = bpy.context.scene.BIMWorkScheduleProperties
|
|
settings = {
|
|
"viz_start": parser.parse(props.visualisation_start, dayfirst=True, fuzzy=True)
|
|
if props.visualisation_start
|
|
else None,
|
|
"viz_finish": parser.parse(props.visualisation_finish, dayfirst=True, fuzzy=True)
|
|
if props.visualisation_finish
|
|
else None,
|
|
"start_frame": bpy.context.scene.frame_start,
|
|
"end_frame": bpy.context.scene.frame_end,
|
|
}
|
|
|
|
material_progress, material_full = get_animation_materials()
|
|
empty = bpy.data.objects.new("collection_origin", None)
|
|
link_collection(empty, collection)
|
|
for task in tasks:
|
|
task_data = process_task_data(task, settings)
|
|
if task_data:
|
|
position_shift = task_data["start_frame"] * size_to_duration_ratio
|
|
bar_size = (task_data["finish_frame"] - task_data["start_frame"]) * size_to_duration_ratio
|
|
|
|
color_progress = bpy.context.scene.BIMAnimationProperties.color_progress
|
|
bar = add_bar(
|
|
material=material_progress,
|
|
vertical_increment=vertical_increment,
|
|
collection=collection,
|
|
parent=empty,
|
|
task=task_data,
|
|
scale=True,
|
|
color=(color_progress.r, color_progress.g, color_progress.b, 1.0),
|
|
shift_x=position_shift,
|
|
name=task_data["name"] + "/Progress Bar",
|
|
)
|
|
|
|
color_full = bpy.context.scene.BIMAnimationProperties.color_full
|
|
bar2 = add_bar(
|
|
material=material_full,
|
|
vertical_increment=vertical_increment,
|
|
parent=empty,
|
|
collection=collection,
|
|
task=task_data,
|
|
color=(color_full.r, color_full.g, color_full.b, 1.0),
|
|
shift_x=position_shift,
|
|
name=task_data["name"] + "/Full Bar",
|
|
)
|
|
bar2.color = (color_full.r, color_full.g, color_full.b, 1.0)
|
|
|
|
bar2.scale = (full_bar_thickness, bar_size, 1)
|
|
shift_object(bar2, y=((size + full_bar_thickness) / 2))
|
|
|
|
start_text = add_text(
|
|
task_data["start_date"].strftime("%d/%m/%y"),
|
|
0,
|
|
"RIGHT",
|
|
vertical_increment,
|
|
parent=empty,
|
|
collection=collection,
|
|
)
|
|
start_text.name = task_data["name"] + "/Start Date"
|
|
shift_object(start_text, x=position_shift - margin, y=-(size + full_bar_thickness))
|
|
|
|
task_text = add_text(
|
|
task_data["name"],
|
|
0,
|
|
"RIGHT",
|
|
vertical_increment,
|
|
parent=empty,
|
|
collection=collection,
|
|
)
|
|
task_text.name = task_data["name"] + "/Task Name"
|
|
shift_object(task_text, x=position_shift, y=0.2)
|
|
finish_text = add_text(
|
|
task_data["finish_date"].strftime("%d/%m/%y"),
|
|
bar_size,
|
|
"LEFT",
|
|
vertical_increment,
|
|
parent=empty,
|
|
collection=collection,
|
|
)
|
|
finish_text.name = task_data["name"] + "/Finish Date"
|
|
shift_object(finish_text, x=position_shift + margin, y=-(size + full_bar_thickness))
|
|
vertical_increment += vertical_spacing
|
|
return empty.select_set(True) if empty else None
|
|
|
|
def set_material(name, r, g, b):
|
|
material = bpy.data.materials.new(name)
|
|
material.use_nodes = True
|
|
tool.Blender.get_material_node(material, "BSDF_PRINCIPLED").inputs[0].default_value = (r, g, b, 1.0)
|
|
return material
|
|
|
|
def get_animation_materials():
|
|
if "color_progress" in bpy.data.materials:
|
|
material_progress = bpy.data.materials["color_progress"]
|
|
else:
|
|
material_progress = set_material("color_progress", 0.0, 1.0, 0.0)
|
|
if "color_full" in bpy.data.materials:
|
|
material_full = bpy.data.materials["color_full"]
|
|
else:
|
|
material_full = set_material("color_full", 1.0, 0.0, 0.0)
|
|
return material_progress, material_full
|
|
|
|
def animate_scale(bar, task):
|
|
scale = (1, size_to_duration_ratio, 1)
|
|
bar.scale = scale
|
|
bar.keyframe_insert(data_path="scale", frame=task["start_frame"])
|
|
scale2 = (1, (task["finish_frame"] - task["start_frame"]) * size_to_duration_ratio, 1)
|
|
bar.scale = scale2
|
|
bar.keyframe_insert(data_path="scale", frame=task["finish_frame"])
|
|
|
|
def animate_color(bar, task, color):
|
|
bar.keyframe_insert(data_path="color", frame=task["start_frame"])
|
|
bar.color = color
|
|
bar.keyframe_insert(data_path="color", frame=task["start_frame"] + 1)
|
|
bar.color = color
|
|
|
|
def place_bar(bar, vertical_increment):
|
|
for vertex in bar.data.vertices:
|
|
vertex.co[1] += 0.5
|
|
bar.rotation_euler[2] = -1.5708
|
|
shift_object(bar, y=-vertical_increment)
|
|
|
|
def shift_object(obj, x=0.0, y=0.0, z=0.0):
|
|
vec = mathutils.Vector((x, y, z))
|
|
inv = obj.matrix_world.copy()
|
|
inv.invert()
|
|
vec_rot = vec @ inv
|
|
obj.location = obj.location + vec_rot
|
|
|
|
def link_collection(obj, collection):
|
|
if collection:
|
|
collection.objects.link(obj)
|
|
if obj.name in bpy.context.scene.collection.objects.keys():
|
|
bpy.context.scene.collection.objects.unlink(obj)
|
|
return obj
|
|
|
|
def create_plane(material, collection, vertical_increment):
|
|
x = 0.5
|
|
y = 0.5
|
|
vert = [(-x, -y, 0.0), (x, -y, 0.0), (-x, y, 0.0), (x, y, 0.0)]
|
|
fac = [(0, 1, 3, 2)]
|
|
mesh = bpy.data.meshes.new("PL")
|
|
mesh.from_pydata(vert, [], fac)
|
|
obj = bpy.data.objects.new("PL", mesh)
|
|
obj.data.materials.append(material)
|
|
place_bar(obj, vertical_increment)
|
|
link_collection(obj, collection)
|
|
return obj
|
|
|
|
def add_text(text, x_position, align, vertical_increment, parent=None, collection=None):
|
|
data = bpy.data.curves.new(type="FONT", name="Timeline")
|
|
data.align_x = align
|
|
data.align_y = "CENTER"
|
|
|
|
data.body = text
|
|
obj = bpy.data.objects.new(name="Unnamed", object_data=data)
|
|
link_collection(obj, collection)
|
|
shift_object(obj, x=x_position, y=-(vertical_increment - 1))
|
|
if parent:
|
|
obj.parent = parent
|
|
return obj
|
|
|
|
def add_bar(
|
|
material,
|
|
vertical_increment,
|
|
parent=None,
|
|
collection=None,
|
|
task=None,
|
|
color=False,
|
|
scale=False,
|
|
shift_x=None,
|
|
name=None,
|
|
):
|
|
plane = create_plane(material, collection, vertical_increment)
|
|
if parent:
|
|
plane.parent = parent
|
|
if color:
|
|
animate_color(plane, task, color)
|
|
if scale:
|
|
animate_scale(plane, task)
|
|
if shift_x:
|
|
shift_object(plane, x=shift_x)
|
|
if name:
|
|
plane.name = name
|
|
return plane
|
|
|
|
if "Bar Visual" in bpy.data.collections:
|
|
collection = bpy.data.collections["Bar Visual"]
|
|
for obj in collection.objects:
|
|
bpy.data.objects.remove(obj)
|
|
|
|
else:
|
|
collection = bpy.data.collections.new("Bar Visual")
|
|
bpy.context.scene.collection.children.link(collection)
|
|
|
|
if tasks:
|
|
create_task_bar_data(tasks, vertical_increment, collection)
|
|
|
|
@classmethod
|
|
def enable_editing_task_animation_colors(cls):
|
|
bpy.context.scene.BIMAnimationProperties.is_editing = True
|
|
|
|
@classmethod
|
|
def load_task_animation_colors(cls):
|
|
props = bpy.context.scene.BIMAnimationProperties
|
|
if not props.task_colors_components_inputs:
|
|
if tool.Ifc.schema():
|
|
# for attribute in tool.Ifc.schema().declaration_by_name("IfcTask").all_attributes():
|
|
# if attribute.name() != "PredefinedType":
|
|
# continue
|
|
# task_types = ifcopenshell.util.attribute.get_enum_items(attribute)
|
|
# return [(e, e, "") for e in enum_items]
|
|
groups = {
|
|
"CREATION": {
|
|
"PredefinedType": ["CONSTRUCTION", "INSTALLATION"],
|
|
"Color": (0.0, 1.0, 0.0),
|
|
},
|
|
"OPERATION": {
|
|
"PredefinedType": ["ATTENDANCE", "MAINTENANCE", "OPERATION", "RENOVATION"],
|
|
"Color": (0.0, 0.0, 1.0),
|
|
},
|
|
"MOVEMENT_TO": {
|
|
"PredefinedType": ["LOGISTIC", "MOVE"],
|
|
"Color": (1.0, 1.0, 0.0),
|
|
},
|
|
"DESTRUCTION": {
|
|
"PredefinedType": ["DEMOLITION", "DISMANTLE", "DISPOSAL", "REMOVAL"],
|
|
"Color": (1.0, 0.0, 0.0),
|
|
},
|
|
"MOVEMENT_FROM": {
|
|
"PredefinedType": ["LOGISTIC", "MOVE"],
|
|
"Color": (1.0, 0.5, 0.0),
|
|
},
|
|
"USERDEFINED": {
|
|
"PredefinedType": ["USERDEFINED", "NOTDEFINED"],
|
|
"Color": (0.2, 0.2, 0.2),
|
|
},
|
|
}
|
|
for group, data in groups.items():
|
|
for predefined_type in data["PredefinedType"]:
|
|
if group in ["CREATION", "OPERATION", "MOVEMENT_TO"]:
|
|
predefined_type_item = props.task_colors_components_outputs.add()
|
|
elif group in ["MOVEMENT_FROM", "DESTRUCTION"]:
|
|
predefined_type_item = props.task_colors_components_inputs.add()
|
|
elif group == "USERDEFINED":
|
|
predefined_type_item = props.task_colors_components_inputs.add()
|
|
predefined_type_item2 = props.task_colors_components_outputs.add()
|
|
predefined_type_item2.name = predefined_type
|
|
predefined_type_item2.color = data["Color"]
|
|
# TO DO: consider cases where users confuses inputs and outputs
|
|
predefined_type_item.name = predefined_type
|
|
predefined_type_item.color = data["Color"]
|
|
|
|
@classmethod
|
|
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):
|
|
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):
|
|
if props.speed_types == "FRAME_SPEED":
|
|
return calculate_using_frames(
|
|
start,
|
|
finish,
|
|
props.speed_animation_frames,
|
|
ifcopenshell.util.date.parse_duration(props.speed_real_duration),
|
|
)
|
|
elif props.speed_types == "DURATION_SPEED":
|
|
animation_duration = ifcopenshell.util.date.parse_duration(props.speed_animation_duration)
|
|
real_duration = ifcopenshell.util.date.parse_duration(props.speed_real_duration)
|
|
return calculate_using_duration(
|
|
start,
|
|
finish,
|
|
fps,
|
|
animation_duration,
|
|
real_duration,
|
|
)
|
|
elif props.speed_types == "MULTIPLIER_SPEED":
|
|
return calculate_using_multiplier(
|
|
start,
|
|
finish,
|
|
1,
|
|
props.speed_multiplier,
|
|
)
|
|
|
|
def calculate_using_multiplier(start, finish, fps, multiplier):
|
|
animation_time = (finish - start) / multiplier
|
|
return animation_time.total_seconds() * fps
|
|
|
|
def calculate_using_duration(start, finish, fps, animation_duration, real_duration):
|
|
return calculate_using_multiplier(start, finish, fps, real_duration / animation_duration)
|
|
|
|
def calculate_using_frames(start, finish, animation_frames, real_duration):
|
|
return ((finish - start) / real_duration) * animation_frames
|
|
|
|
props = bpy.context.scene.BIMWorkScheduleProperties
|
|
if not (props.visualisation_start and props.visualisation_finish):
|
|
return
|
|
|
|
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)
|
|
return {
|
|
"start": start,
|
|
"finish": finish,
|
|
"duration": duration,
|
|
"start_frame": start_frame,
|
|
"total_frames": total_frames,
|
|
}
|
|
|
|
@classmethod
|
|
def get_animation_product_frames(cls, work_schedule, settings):
|
|
def preprocess_task(task):
|
|
for subtask in ifcopenshell.util.sequence.get_nested_tasks(task):
|
|
preprocess_task(subtask)
|
|
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
|
|
for output in ifcopenshell.util.sequence.get_task_outputs(task):
|
|
add_product_frame(output.id(), task.PredefinedType, start, finish, "output")
|
|
for input in cls.get_task_inputs(task):
|
|
add_product_frame(input.id(), task.PredefinedType, start, finish, "input")
|
|
|
|
def add_product_frame(product_id, type, product_start, product_finish, relationship):
|
|
product_frames.setdefault(product_id, []).append(
|
|
{
|
|
"type": type,
|
|
"relationship": relationship,
|
|
"STARTED": round(
|
|
settings["start_frame"]
|
|
+ (((product_start - settings["start"]) / settings["duration"]) * settings["total_frames"])
|
|
),
|
|
"COMPLETED": round(
|
|
settings["start_frame"]
|
|
+ (((product_finish - settings["start"]) / settings["duration"]) * settings["total_frames"])
|
|
),
|
|
}
|
|
)
|
|
|
|
product_frames = {}
|
|
for root_task in ifcopenshell.util.sequence.get_root_tasks(work_schedule):
|
|
preprocess_task(root_task)
|
|
return product_frames
|
|
|
|
@classmethod
|
|
def clear_object_animation(cls, obj):
|
|
if obj.animation_data:
|
|
obj.animation_data_clear()
|
|
|
|
@classmethod
|
|
def clear_object_color(cls, obj):
|
|
obj.color = (1.0, 1.0, 1.0, 1.0)
|
|
|
|
@classmethod
|
|
def display_object(cls, obj):
|
|
if not obj.visible_get():
|
|
obj.hide_viewport = False
|
|
obj.hide_render = False
|
|
|
|
@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)
|
|
cls.clear_object_color(obj)
|
|
cls.display_object(obj)
|
|
|
|
@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 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, 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"] + 1)
|
|
|
|
@classmethod
|
|
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
|
|
cls.animate_destruction(obj, start_frame, product_frame, color, animation_type)
|
|
|
|
@classmethod
|
|
def animate_output(cls, obj, start_frame, product_frame):
|
|
props = bpy.context.scene.BIMAnimationProperties
|
|
color = props.task_colors_components_outputs[product_frame["type"]].color
|
|
if product_frame["type"] in ["CONSTRUCTION", "INSTALLATION", "NOTDEFINED"]:
|
|
cls.animate_creation(obj, start_frame, product_frame, color)
|
|
elif product_frame["type"] in ["ATTENDANCE", "MAINTENANCE", "OPERATION", "RENOVATION"]:
|
|
cls.animate_operation(obj, start_frame, product_frame, color)
|
|
elif product_frame["type"] in ["LOGISTIC", "MOVE"]:
|
|
cls.animate_movement_to(obj, start_frame, product_frame, color)
|
|
else:
|
|
cls.animate_operation(obj, start_frame, product_frame, color)
|
|
|
|
@classmethod
|
|
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
|
|
obj.hide_render = False
|
|
obj.keyframe_insert(data_path="color", frame=start_frame)
|
|
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"]
|
|
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=start + 1)
|
|
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"])
|
|
|
|
@classmethod
|
|
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, animation_type):
|
|
cls.animate_destruction(obj, start_frame, product_frame, color, animation_type)
|
|
|
|
@classmethod
|
|
def animate_creation(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"])
|
|
|
|
@classmethod
|
|
def animate_operation(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.keyframe_insert(data_path="color", 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.color = (1.0, 1.0, 1.0, 1)
|
|
obj.keyframe_insert(data_path="color", frame=product_frame["COMPLETED"])
|
|
|
|
@classmethod
|
|
def animate_movement_to(cls, obj, start_frame, product_frame, color):
|
|
cls.animate_creation(obj, start_frame, product_frame, color)
|
|
|
|
@classmethod
|
|
def add_text_animation_handler(cls, settings):
|
|
def remove_handler(function):
|
|
bpy.app.handlers.frame_change_post.remove(function)
|
|
|
|
def append_handler(function):
|
|
bpy.app.handlers.frame_change_post.append(function)
|
|
|
|
def animate_text_handler(scene):
|
|
data = bpy.data.curves.get("Timeline")
|
|
if not data or not bpy.data.objects.get("Timeline"):
|
|
remove_handler(animate_text_handler)
|
|
data.body = get_frame_date(scene, data.BIMDateTextProperties)
|
|
|
|
def get_frame_date(scene, props):
|
|
start = parser.parse(props.start, dayfirst=True, fuzzy=True)
|
|
finish = parser.parse(props.finish, dayfirst=True, fuzzy=True)
|
|
duration = finish - start
|
|
frame_date = (((scene.frame_current - props.start_frame) / props.total_frames) * duration) + start
|
|
return frame_date.date().isoformat()
|
|
|
|
data = bpy.data.curves.get("Timeline")
|
|
if not data:
|
|
data = bpy.data.curves.new(type="FONT", name="Timeline")
|
|
obj = bpy.data.objects.get("Timeline")
|
|
if not obj:
|
|
obj = bpy.data.objects.new(name="Timeline", object_data=data)
|
|
bpy.context.scene.collection.objects.link(obj)
|
|
|
|
obj.data.BIMDateTextProperties.start_frame = settings["start_frame"]
|
|
obj.data.BIMDateTextProperties.total_frames = int(settings["total_frames"])
|
|
obj.data.BIMDateTextProperties.start = bpy.context.scene.BIMWorkScheduleProperties.visualisation_start
|
|
obj.data.BIMDateTextProperties.finish = bpy.context.scene.BIMWorkScheduleProperties.visualisation_finish
|
|
append_handler(animate_text_handler)
|
|
|
|
@classmethod
|
|
def create_tasks_json(cls, work_schedule=None):
|
|
sequence_type_map = {
|
|
None: "FS",
|
|
"START_START": "SS",
|
|
"START_FINISH": "SF",
|
|
"FINISH_START": "FS",
|
|
"FINISH_FINISH": "FF",
|
|
"USERDEFINED": "FS",
|
|
"NOTDEFINED": "FS",
|
|
}
|
|
is_baseline = False
|
|
if work_schedule.PredefinedType == "BASELINE":
|
|
is_baseline = True
|
|
relating_work_schedule = work_schedule.IsDeclaredBy[0].RelatingObject
|
|
work_schedule = relating_work_schedule
|
|
tasks_json = []
|
|
for task in ifcopenshell.util.sequence.get_root_tasks(work_schedule):
|
|
if is_baseline:
|
|
cls.create_new_task_json(task, tasks_json, sequence_type_map, baseline_schedule=work_schedule)
|
|
else:
|
|
cls.create_new_task_json(task, tasks_json, sequence_type_map)
|
|
return tasks_json
|
|
|
|
@classmethod
|
|
def create_new_task_json(cls, task, json, type_map=None, baseline_schedule=None):
|
|
task_time = task.TaskTime
|
|
resources = ifcopenshell.util.sequence.get_task_resources(task, is_deep=False)
|
|
|
|
string_resources = ""
|
|
resources_usage = ""
|
|
for resource in resources:
|
|
string_resources += resource.Name + ", "
|
|
resources_usage += str(resource.Usage.ScheduleUsage) + ", " if resource.Usage else "-, "
|
|
|
|
schedule_start = task_time.ScheduleStart if task_time else ""
|
|
schedule_finish = task_time.ScheduleFinish if task_time else ""
|
|
|
|
baseline_task = None
|
|
if baseline_schedule:
|
|
for rel in task.Declares:
|
|
for baseline_task in rel.RelatedObjects:
|
|
if baseline_schedule.id() == ifcopenshell.util.sequence.get_task_work_schedule(baseline_task).id():
|
|
baseline_task = task
|
|
break
|
|
|
|
if baseline_task and baseline_task.TaskTime:
|
|
compare_start = baseline_task.TaskTime.ScheduleStart
|
|
compare_finish = baseline_task.TaskTime.ScheduleFinish
|
|
else:
|
|
compare_start = schedule_start
|
|
compare_finish = schedule_finish
|
|
task_name = task.Name or "Unnamed"
|
|
task_name = task_name.replace("\n", "")
|
|
data = {
|
|
"pID": task.id(),
|
|
"pName": task_name,
|
|
"pCaption": task_name,
|
|
"pStart": schedule_start,
|
|
"pEnd": schedule_finish,
|
|
"pPlanStart": compare_start,
|
|
"pPlanEnd": compare_finish,
|
|
"pMile": 1 if task.IsMilestone else 0,
|
|
"pRes": string_resources,
|
|
"pComp": 0,
|
|
"pGroup": 1 if task.IsNestedBy else 0,
|
|
"pParent": task.Nests[0].RelatingObject.id() if task.Nests else 0,
|
|
"pOpen": 1,
|
|
"pCost": 1,
|
|
"ifcduration": str(ifcopenshell.util.date.ifc2datetime(task_time.ScheduleDuration))
|
|
if (task_time and task_time.ScheduleDuration)
|
|
else "",
|
|
"resourceUsage": resources_usage,
|
|
}
|
|
if task_time and task_time.IsCritical:
|
|
data["pClass"] = "gtaskred"
|
|
elif data["pGroup"]:
|
|
data["pClass"] = "ggroupblack"
|
|
elif data["pMile"]:
|
|
data["pClass"] = "gmilestone"
|
|
else:
|
|
data["pClass"] = "gtaskblue"
|
|
|
|
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, baseline_schedule)
|
|
|
|
@classmethod
|
|
def generate_gantt_browser_chart(cls, task_json, work_schedule):
|
|
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), "data": json.dumps(work_schedule.get_info())}
|
|
)
|
|
)
|
|
webbrowser.open("file://" + os.path.join(bpy.context.scene.BIMProperties.data_dir, "gantt", "index.html"))
|
|
|
|
@classmethod
|
|
def is_filter_by_active_schedule(cls):
|
|
return bpy.context.scene.BIMWorkScheduleProperties.filter_by_active_schedule
|
|
|
|
@classmethod
|
|
def get_tasks_for_product(cls, product, work_schedule=None):
|
|
return ifcopenshell.util.sequence.get_tasks_for_product(product, work_schedule)
|
|
|
|
@classmethod
|
|
def load_product_related_tasks(cls, task_inputs, task_ouputs):
|
|
props = bpy.context.scene.BIMWorkScheduleProperties
|
|
props.product_input_tasks.clear()
|
|
props.product_output_tasks.clear()
|
|
for task in task_inputs or []:
|
|
new = props.product_input_tasks.add()
|
|
new.name = task.Name or "Unnamed"
|
|
new.ifc_definition_id = task.id()
|
|
for task in task_ouputs or []:
|
|
new = props.product_output_tasks.add()
|
|
new.name = task.Name or "Unnamed"
|
|
new.ifc_definition_id = task.id()
|
|
|
|
@classmethod
|
|
def get_work_schedule_products(cls, work_schedule):
|
|
products = []
|
|
for task in ifcopenshell.util.sequence.get_root_tasks(work_schedule):
|
|
products.extend(ifcopenshell.util.sequence.get_task_inputs(task, is_deep=True))
|
|
products.extend(ifcopenshell.util.sequence.get_task_outputs(task, is_deep=True))
|
|
return products
|
|
|
|
@classmethod
|
|
def has_task_assignments(cls, product, work_schedule=None):
|
|
task_inputs, task_ouputs = ifcopenshell.util.sequence.get_tasks_for_product(product)
|
|
if work_schedule:
|
|
task_inputs = [task for task in task_inputs or [] if cls.get_work_schedule(task) == work_schedule]
|
|
task_ouputs = [task for task in task_ouputs or [] if cls.get_work_schedule(task) == work_schedule]
|
|
return bool(task_inputs or task_ouputs)
|
|
|
|
@classmethod
|
|
def is_sorting_enabled(cls):
|
|
return bpy.context.scene.BIMWorkScheduleProperties.sort_column
|
|
|
|
@classmethod
|
|
def is_sort_reversed(cls):
|
|
return bpy.context.scene.BIMWorkScheduleProperties.is_sort_reversed
|
|
|
|
@classmethod
|
|
def get_user_predefined_type(cls):
|
|
predefined_type = bpy.context.scene.BIMWorkScheduleProperties.work_schedule_predefined_types
|
|
object_type = None
|
|
if predefined_type == "USERDEFINED":
|
|
object_type = bpy.context.scene.BIMWorkScheduleProperties.object_type
|
|
return predefined_type, object_type
|
|
|
|
@classmethod
|
|
def add_animation_camera(cls):
|
|
bpy.ops.object.camera_add()
|
|
camera = bpy.context.active_object
|
|
camera.data.lens = 26
|
|
camera.name = "4D Camera"
|
|
camera.location = mathutils.Vector((15, 0, 15))
|
|
camera.rotation_euler = mathutils.Euler((1.2, 0, 1.5), "XYZ")
|
|
for obj in bpy.context.scene.objects:
|
|
obj.select_set(False)
|
|
for obj in bpy.context.visible_objects:
|
|
if not (obj.hide_get() or obj.hide_render) and obj.type != "LIGHT":
|
|
obj.select_set(True)
|
|
bpy.context.scene.camera = camera
|
|
bpy.ops.view3d.camera_to_view_selected()
|