Updating a task duration now auto calculates the scheduled finish times based on the working calendar

This commit is contained in:
Dion Moult
2021-05-11 11:35:47 +10:00
parent dd6f593e26
commit 353281fa4d
5 changed files with 145 additions and 89 deletions
@@ -5,78 +5,6 @@ from dateutil import parser
from ifcopenshell.api.sequence.data import Data from ifcopenshell.api.sequence.data import Data
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:
if is_working_day(current_date, calendar):
result += 1
current_date += datetime.timedelta(days=1)
return result
def is_working_day(day, calendar):
is_working_day = False
for work_time_id in calendar["WorkingTimes"] or []:
if is_work_time_applicable_to_day(Data.work_times[work_time_id], day):
is_working_day = True
break
if not is_working_day:
return is_working_day
for work_time_id in calendar["ExceptionTimes"] or []:
if is_work_time_applicable_to_day(Data.work_times[work_time_id], day):
is_working_day = False
break
return is_working_day
def is_work_time_applicable_to_day(work_time, day):
if work_time["Start"] and work_time["Start"] > day:
return False
if work_time["Finish"] and work_time["Finish"] < day:
return False
if not work_time["RecurrencePattern"]:
return True
recurrence = Data.recurrence_patterns[work_time["RecurrencePattern"]]
if recurrence["RecurrenceType"] == "DAILY":
if not recurrence["Interval"] and not recurrence["Occurrences"]:
return True
if not work_time["Start"]:
return False
return False # TODO
elif recurrence["RecurrenceType"] == "WEEKLY":
if not recurrence["Interval"] and not recurrence["Occurrences"]:
return (day.weekday() + 1) in recurrence["WeekdayComponent"]
if not work_time["Start"]:
return False
return False # TODO
elif recurrence["RecurrenceType"] == "MONTHLY_BY_DAY_OF_MONTH":
if not recurrence["Interval"] and not recurrence["Occurrences"]:
return day.day in recurrence["DayComponent"]
return False # TODO
elif recurrence["RecurrenceType"] == "MONTHLY_BY_POSITION":
if not recurrence["Interval"] and not recurrence["Occurrences"]:
return (day.weekday() + 1) in recurrence["WeekdayComponent"] and math.floor(day.day / 7) + 1 == recurrence[
"Position"
]
return False # TODO
elif recurrence["RecurrenceType"] == "YEARLY_BY_DAY_OF_MONTH":
if not recurrence["Interval"] and not recurrence["Occurrences"]:
return day.month in recurrence["MonthComponent"] and day.day in recurrence["DayComponent"]
return False # TODO
elif recurrence["RecurrenceType"] == "YEARLY_BY_POSITION":
if not recurrence["Interval"] and not recurrence["Occurrences"]:
return (
day.month in recurrence["MonthComponent"]
and (day.weekday() + 1) in recurrence["WeekdayComponent"]
and math.floor(day.day / 7) + 1 == recurrence["Position"]
)
return False # TODO
def derive_date(ifc_definition_id, attribute_name, date=None, is_earliest=False, is_latest=False): def derive_date(ifc_definition_id, attribute_name, date=None, is_earliest=False, is_latest=False):
@@ -96,14 +24,6 @@ def derive_date(ifc_definition_id, attribute_name, date=None, is_earliest=False,
return date return date
def derive_calendar(ifc_definition_id):
task = Data.tasks[ifc_definition_id]
if task["HasAssignmentsWorkCalendar"]:
return Data.work_calendars[task["HasAssignmentsWorkCalendar"][0]]
if task["Nests"]:
return derive_calendar(task["Nests"][0])
def derive_duration(ifc_definition_id, attribute_name): def derive_duration(ifc_definition_id, attribute_name):
task = Data.tasks[ifc_definition_id] task = Data.tasks[ifc_definition_id]
if task["TaskTime"]: if task["TaskTime"]:
@@ -8,6 +8,7 @@ import pystache
import webbrowser import webbrowser
import ifcopenshell.api import ifcopenshell.api
import ifcopenshell.util.date import ifcopenshell.util.date
import ifcopenshell.util.sequence
import blenderbim.bim.helper import blenderbim.bim.helper
import blenderbim.bim.module.sequence.helper as helper import blenderbim.bim.module.sequence.helper as helper
from datetime import datetime from datetime import datetime
@@ -275,6 +276,7 @@ class LoadTaskProperties(bpy.types.Operator):
task: bpy.props.IntProperty() task: bpy.props.IntProperty()
def execute(self, context): def execute(self, context):
self.file = IfcStore.get_file()
self.props = context.scene.BIMWorkScheduleProperties self.props = context.scene.BIMWorkScheduleProperties
self.tprops = context.scene.BIMTaskTreeProperties self.tprops = context.scene.BIMTaskTreeProperties
self.props.is_task_update_enabled = False self.props.is_task_update_enabled = False
@@ -292,7 +294,9 @@ class LoadTaskProperties(bpy.types.Operator):
Data.sequences[r]["RelatingProcess"] for r in task["IsSuccessorFrom"] Data.sequences[r]["RelatingProcess"] for r in task["IsSuccessorFrom"]
] ]
calendar = helper.derive_calendar(item.ifc_definition_id) calendar = ifcopenshell.util.sequence.derive_calendar(self.file.by_id(item.ifc_definition_id))
if calendar:
calendar = Data.work_calendars[calendar.id()]
if task["HasAssignmentsWorkCalendar"]: if task["HasAssignmentsWorkCalendar"]:
item.calendar = calendar["Name"] or "Unnamed" item.calendar = calendar["Name"] or "Unnamed"
else: else:
@@ -312,7 +316,9 @@ class LoadTaskProperties(bpy.types.Operator):
item.derived_start = self.canonicalise_time(derived_start) if derived_start else "" item.derived_start = self.canonicalise_time(derived_start) if derived_start else ""
item.derived_finish = self.canonicalise_time(derived_finish) if derived_finish else "" item.derived_finish = self.canonicalise_time(derived_finish) if derived_finish else ""
if derived_start and derived_finish and calendar: if derived_start and derived_finish and calendar:
derived_duration = helper.count_working_days(derived_start, derived_finish, calendar) derived_duration = ifcopenshell.util.sequence.count_working_days(
derived_start, derived_finish, self.file.by_id(calendar["id"])
)
item.derived_duration = f"P{derived_duration}D" item.derived_duration = f"P{derived_duration}D"
item.start = "-" item.start = "-"
item.finish = "-" item.finish = "-"
@@ -460,7 +466,6 @@ class EditTaskTime(bpy.types.Operator):
def execute(self, context): def execute(self, context):
props = context.scene.BIMWorkScheduleProperties props = context.scene.BIMWorkScheduleProperties
attributes = blenderbim.bim.helper.export_attributes(props.task_time_attributes, self.export_attributes) attributes = blenderbim.bim.helper.export_attributes(props.task_time_attributes, self.export_attributes)
attributes = self.convert_strings_to_date_times(attributes)
self.file = IfcStore.get_file() self.file = IfcStore.get_file()
ifcopenshell.api.run( ifcopenshell.api.run(
@@ -475,10 +480,10 @@ class EditTaskTime(bpy.types.Operator):
def export_attributes(self, attributes, prop): def export_attributes(self, attributes, prop):
if "Start" in prop.name or "Finish" in prop.name or prop.name == "StatusTime": if "Start" in prop.name or "Finish" in prop.name or prop.name == "StatusTime":
attributes[prop.name] = helper.parse_datetime(value) attributes[prop.name] = helper.parse_datetime(prop.string_value)
return True return True
elif prop.name == "ScheduleDuration": elif prop.name == "ScheduleDuration":
attributes[prop.name] = helper.parse_duration(value) attributes[prop.name] = helper.parse_duration(prop.string_value)
return True return True
@@ -1,4 +1,6 @@
import datetime
import ifcopenshell.util.date import ifcopenshell.util.date
import ifcopenshell.util.sequence
class Usecase: class Usecase:
@@ -9,10 +11,43 @@ class Usecase:
self.settings[key] = value self.settings[key] = value
def execute(self): def execute(self):
# If the user specifies both an end date and a duration, the duration takes priority
if "ScheduleDuration" in self.settings["attributes"].keys():
del self.settings["attributes"]["ScheduleFinish"]
if "ActualDuration" in self.settings["attributes"].keys():
del self.settings["attributes"]["ActualFinish"]
for name, value in self.settings["attributes"].items(): for name, value in self.settings["attributes"].items():
if value: if value:
if "Start" in name or "Finish" in name or name == "StatusTime": if "Start" in name or "Finish" in name or name == "StatusTime":
value = ifcopenshell.util.date.datetime2ifc(value, "IfcDateTime") value = ifcopenshell.util.date.datetime2ifc(value, "IfcDateTime")
elif name == "ScheduleDuration": elif name == "ScheduleDuration" or name == "ActualDuration" or name == "RemainingTime":
value = ifcopenshell.util.date.datetime2ifc(value, "IfcDuration") value = ifcopenshell.util.date.datetime2ifc(value, "IfcDuration")
setattr(self.settings["task_time"], name, value) setattr(self.settings["task_time"], name, value)
if (
"ScheduleDuration" in self.settings["attributes"].keys()
and self.settings["task_time"].ScheduleDuration
and self.settings["task_time"].ScheduleStart
):
start = ifcopenshell.util.date.ifc2datetime(self.settings["task_time"].ScheduleStart)
current_date = datetime.date(start.year, start.month, start.day)
duration = ifcopenshell.util.date.ifc2datetime(self.settings["task_time"].ScheduleDuration).days
task = [e for e in self.file.get_inverse(self.settings["task_time"]) if e.is_a("IfcTask")]
if not task:
return
else:
task = task[0]
calendar = ifcopenshell.util.sequence.derive_calendar(task)
while duration >= 0:
if self.settings["task_time"].DurationType == "ELAPSEDTIME" or not calendar:
duration -= 1
elif ifcopenshell.util.sequence.is_working_day(current_date, calendar):
duration -= 1
current_date += datetime.timedelta(days=1)
current_date -= datetime.timedelta(days=1)
self.settings["task_time"].ScheduleFinish = ifcopenshell.util.date.datetime2ifc(current_date, "IfcDateTime")
@@ -0,0 +1,96 @@
import datetime
import ifcopenshell.util.date
def derive_calendar(task):
calendar = [
rel.RelatingControl
for rel in task.HasAssignments or []
if rel.is_a("IfcRelAssignsToControl") and rel.RelatingControl.is_a("IfcWorkCalendar")
]
if calendar:
return calendar[0]
for rel in task.Nests or []:
return derive_calendar(rel.RelatingObject)
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:
if is_working_day(current_date, calendar):
result += 1
current_date += datetime.timedelta(days=1)
return result
def is_working_day(day, calendar):
is_working_day = False
for work_time in calendar.WorkingTimes or []:
if is_work_time_applicable_to_day(work_time, day):
is_working_day = True
break
if not is_working_day:
return is_working_day
for work_time in calendar.ExceptionTimes or []:
if is_work_time_applicable_to_day(work_time, day):
is_working_day = False
break
print('is day working day ', day, is_working_day)
return is_working_day
def is_work_time_applicable_to_day(work_time, day):
start = None
finish = None
if work_time.Start:
start = ifcopenshell.util.date.ifc2datetime(work_time.Start)
if start > day:
return False
if work_time.Finish:
finish = ifcopenshell.util.date.ifc2datetime(work_time.Finish)
if finish < day:
return False
if not work_time.RecurrencePattern:
return True
recurrence = work_time.RecurrencePattern
if recurrence.RecurrenceType == "DAILY":
if not recurrence.Interval and not recurrence.Occurrences:
return True
if not work_time.Start:
return False
return False # TODO
elif recurrence.RecurrenceType == "WEEKLY":
if not recurrence.Interval and not recurrence.Occurrences:
return (day.weekday() + 1) in recurrence.WeekdayComponent
if not work_time.Start:
return False
return False # TODO
elif recurrence.RecurrenceType == "MONTHLY_BY_DAY_OF_MONTH":
if not recurrence.Interval and not recurrence.Occurrences:
return day.day in recurrence.DayComponent
return False # TODO
elif recurrence.RecurrenceType == "MONTHLY_BY_POSITION":
if not recurrence.Interval and not recurrence.Occurrences:
return (day.weekday() + 1) in recurrence.WeekdayComponent and math.floor(day.day / 7) + 1 == recurrence[
"Position"
]
return False # TODO
elif recurrence.RecurrenceType == "YEARLY_BY_DAY_OF_MONTH":
if not recurrence.Interval and not recurrence.Occurrences:
return day.month in recurrence.MonthComponent and day.day in recurrence.DayComponent
return False # TODO
elif recurrence.RecurrenceType == "YEARLY_BY_POSITION":
if not recurrence.Interval and not recurrence.Occurrences:
return (
day.month in recurrence.MonthComponent
and (day.weekday() + 1) in recurrence.WeekdayComponent
and math.floor(day.day / 7) + 1 == recurrence.Position
)
return False # TODO
+3 -3
View File
@@ -166,7 +166,7 @@ class P62Ifc:
def process_working_week(self, week, calendar): def process_working_week(self, week, calendar):
for day in week: for day in week:
if day["ifc"]: if day["ifc"] or not day["WorkTimes"]:
continue continue
day["ifc"] = ifcopenshell.api.run( day["ifc"] = ifcopenshell.api.run(
@@ -346,7 +346,7 @@ class P62Ifc:
"ScheduleFinish": activity["FinishDate"], "ScheduleFinish": activity["FinishDate"],
"DurationType": "WORKTIME" if activity["PlannedDuration"] else None, "DurationType": "WORKTIME" if activity["PlannedDuration"] else None,
"ScheduleDuration": datetime.timedelta( "ScheduleDuration": datetime.timedelta(
days=math.ceil(float(activity["PlannedDuration"]) / float(calendar["HoursPerDay"])) days=float(activity["PlannedDuration"]) / float(calendar["HoursPerDay"])
) )
or None or None
if activity["PlannedDuration"] if activity["PlannedDuration"]
@@ -390,7 +390,7 @@ class P62Ifc:
"sequence.assign_lag_time", "sequence.assign_lag_time",
self.file, self.file,
rel_sequence=rel_sequence, rel_sequence=rel_sequence,
lag_value=datetime.timedelta(days=math.ceil(lag / float(calendar["HoursPerDay"]))), lag_value=datetime.timedelta(days=lag / float(calendar["HoursPerDay"])),
duration_type="WORKTIME", duration_type="WORKTIME",
) )