Various 4D improvements:

- refactor work schedule visualisation operator
- Fix text handler
- Improve durations UI for task times (proposal to replicate for all durations)
- Simplify durations ( P32H as P4D or P1D8H)
- Implement years/months in various datetime calculations
- Feature to customise animation colors per construction task type
- Feature to add Visual task bars
- Feature to customise Visual task bar material colors
- Move helper functions from blenderbim.bim.module.sequence to ifcopenshell.util.sequence
This commit is contained in:
Sigma Dimensions
2023-01-04 02:08:52 +01:00
parent 0c77fb81ce
commit 981b6cd65f
9 changed files with 972 additions and 412 deletions
@@ -21,6 +21,32 @@ import ifcopenshell.util.date
from functools import lru_cache
def derive_date(task, attribute_name, date=None, is_earliest=False, is_latest=False):
if task.TaskTime:
current_date = (
ifcopenshell.util.date.ifc2datetime(getattr(task.TaskTime, attribute_name))
if getattr(task.TaskTime, attribute_name)
else ""
)
if current_date:
return current_date
for subtask in get_all_nested_tasks(task):
current_date = derive_date(
subtask,
attribute_name,
date=date,
is_earliest=is_earliest,
is_latest=is_latest,
)
if is_earliest:
if current_date and (date is None or current_date < date):
date = current_date
if is_latest:
if current_date and (date is None or current_date > date):
date = current_date
return date
def derive_calendar(task):
calendar = [
rel.RelatingControl
@@ -38,7 +64,7 @@ def count_working_days(start, finish, calendar):
result = 0
current_date = datetime.date(start.year, start.month, start.day)
finish_date = datetime.date(finish.year, finish.month, finish.day)
while current_date < finish_date:
while current_date <= finish_date:
if (
calendar
and calendar.WorkingTimes
@@ -58,7 +84,11 @@ def get_start_or_finish_date(
# Typically a milestone will have zero duration, so the start == finish
return start
# We minus 1 because the start day itself is counted as a day
duration = datetime.timedelta(days=duration.days - 1)
months = int(getattr(duration, "months", 0))
years = int(getattr(duration, "years", 0))
total_duration = duration.days + months * 30 + years * 12 * 30
duration = datetime.timedelta(days=total_duration - 1)
if date_type == "START":
duration = -duration
result = offset_date(start, duration, duration_type, calendar)
@@ -69,7 +99,10 @@ def get_start_or_finish_date(
def offset_date(start, duration, duration_type, calendar):
current_date = start
abs_duration = abs(duration.days)
months = getattr(duration, "months", 0)
years = getattr(duration, "years", 0)
abs_duration = abs((duration.days + months * 30 + years * 12 * 30))
date_offset = datetime.timedelta(days=1 if duration.days > 0 else -1)
while abs_duration > 0:
if duration_type == "ELAPSEDTIME" or not is_calendar_applicable(
@@ -199,3 +232,107 @@ def is_work_time_applicable_to_day(work_time, day):
and math.floor(day.day / 7) + 1 == recurrence.Position
)
return False # TODO
def get_task_work_schedule(task):
parent_task = get_parent_task(task)
if parent_task:
get_task_work_schedule(parent_task)
else:
schedules = [
rel.RelatingControl
for rel in task.HasAssignments
if rel.is_a("IfcRelAssignsToControl")
and rel.RelatingControl.is_a("IfcWorkSchedule")
]
return schedules
def get_nested_tasks(task):
return [object for rel in task.IsNestedBy for object in rel.RelatedObjects]
def get_parent_task(task):
return (
task.Nests[0].RelatingObject
if task.Nests and task.Nests[0].RelatingObject.is_a("IfcTask")
else None
)
def get_all_nested_tasks(task):
for nested_task in get_nested_tasks(task):
yield nested_task
yield from get_all_nested_tasks(nested_task)
def get_work_schedule_tasks(work_schedule):
tasks = []
for root_task in get_root_tasks(work_schedule):
nested_tasks = get_all_nested_tasks(root_task)
tasks.extend(nested_tasks)
return tasks
def get_root_tasks(work_schedule):
return [
obj
for rel in work_schedule.Controls
for obj in rel.RelatedObjects
if obj.is_a("IfcTask")
]
def get_root_tasks_ids(work_schedule):
return [
obj.id()
for rel in work_schedule.Controls
for obj in rel.RelatedObjects
if obj.is_a("IfcTask")
]
def guess_date_range(work_schedule):
earliest = None
latest = None
root_tasks = get_root_tasks(work_schedule)
tasks_with_assignements = []
for task in root_tasks:
if has_task_outputs(task):
tasks_with_assignements.append(task)
for sub_task in get_all_nested_tasks(task):
if has_task_outputs(sub_task):
tasks_with_assignements.append(sub_task)
for task in tasks_with_assignements:
derived_start = derive_date(task, "ScheduleStart", is_earliest=True)
derived_finish = derive_date(task, "ScheduleFinish", is_latest=True)
if derived_start and (not earliest or derived_start < earliest):
earliest = derived_start
if derived_finish and (not latest or derived_finish > latest):
latest = derived_finish
return earliest, latest
def get_direct_task_outputs(task):
return [
rel.RelatingProduct
for rel in task.HasAssignments
if rel.is_a("IfcRelAssignsToProduct")
]
def get_task_outputs(task, is_deep=False):
if not is_deep:
return get_direct_task_outputs(task)
else:
nested_tasks = get_all_nested_tasks(task)
return [
output
for nested_task in nested_tasks
for output in get_direct_task_outputs(nested_task)
]
def has_task_outputs(task):
return len(get_task_outputs(task)) > 0