mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-13 02:47:48 +00:00
Generate functions for all API usecases for better static code features. See #2693.
This commit is contained in:
@@ -23,35 +23,38 @@ import ifcopenshell.util.date
|
||||
import ifcopenshell.util.sequence
|
||||
|
||||
|
||||
def recalculate_schedule(file, work_schedule=None) -> None:
|
||||
"""Calculate the critical path and floats for a work schedule
|
||||
|
||||
This implements critical path analysis, using the forward pass and
|
||||
backward pass method. When run, any tasks that have no float will be
|
||||
marked as critical, and both the total and free floats will be
|
||||
populated for all task times.
|
||||
|
||||
Cyclical relationships are detected and will result in a recursion
|
||||
error.
|
||||
|
||||
:param work_schedule: The IfcWorkSchedule to perform the calculation on.
|
||||
:type work_schedule: ifcopenshell.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# See the example for ifcopenshell.api.sequence.cascade_schedule for
|
||||
# details of how to set up a basic set of tasks and calculate the
|
||||
# critical path. Typically cascade_schedule is run prior to ensure
|
||||
# that dates are correct.
|
||||
"""
|
||||
usecase = Usecase()
|
||||
usecase.file = file
|
||||
usecase.settings = {"work_schedule": work_schedule}
|
||||
return usecase.execute()
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, work_schedule=None):
|
||||
"""Calculate the critical path and floats for a work schedule
|
||||
|
||||
This implements critical path analysis, using the forward pass and
|
||||
backward pass method. When run, any tasks that have no float will be
|
||||
marked as critical, and both the total and free floats will be
|
||||
populated for all task times.
|
||||
|
||||
Cyclical relationships are detected and will result in a recursion
|
||||
error.
|
||||
|
||||
:param work_schedule: The IfcWorkSchedule to perform the calculation on.
|
||||
:type work_schedule: ifcopenshell.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# See the example for ifcopenshell.api.sequence.cascade_schedule for
|
||||
# details of how to set up a basic set of tasks and calculate the
|
||||
# critical path. Typically cascade_schedule is run prior to ensure
|
||||
# that dates are correct.
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"work_schedule": work_schedule}
|
||||
|
||||
def execute(self):
|
||||
# The method implemented is the same as shown here:
|
||||
# https://www.youtube.com/watch?v=qTErIV6OqLg
|
||||
@@ -84,9 +87,7 @@ class Usecase:
|
||||
break # We have an infinite loop due to a cyclic graph
|
||||
|
||||
if is_cyclic:
|
||||
raise RecursionError(
|
||||
"Task graph is cyclic and so critical path method cannot be performed."
|
||||
)
|
||||
raise RecursionError("Task graph is cyclic and so critical path method cannot be performed.")
|
||||
return
|
||||
|
||||
self.pending_nodes = set(self.g.nodes)
|
||||
@@ -112,9 +113,7 @@ class Usecase:
|
||||
self.g = nx.DiGraph()
|
||||
self.edges = []
|
||||
self.g.add_node("start", duration=0, duration_type="ELAPSEDTIME", calendar=None)
|
||||
self.g.add_node(
|
||||
"finish", duration=0, duration_type="ELAPSEDTIME", calendar=None
|
||||
)
|
||||
self.g.add_node("finish", duration=0, duration_type="ELAPSEDTIME", calendar=None)
|
||||
for rel in self.settings["work_schedule"].Controls:
|
||||
for related_object in rel.RelatedObjects:
|
||||
if not related_object.is_a("IfcTask"):
|
||||
@@ -129,9 +128,7 @@ class Usecase:
|
||||
return
|
||||
|
||||
if task.TaskTime and task.TaskTime.ScheduleDuration:
|
||||
duration = ifcopenshell.util.date.ifc2datetime(
|
||||
task.TaskTime.ScheduleDuration
|
||||
).days
|
||||
duration = ifcopenshell.util.date.ifc2datetime(task.TaskTime.ScheduleDuration).days
|
||||
duration_type = task.TaskTime.DurationType
|
||||
else:
|
||||
duration = 0
|
||||
@@ -150,11 +147,11 @@ class Usecase:
|
||||
rel.RelatingProcess.id(),
|
||||
task.id(),
|
||||
{
|
||||
"lag_time": 0
|
||||
if not rel.TimeLag
|
||||
else ifcopenshell.util.date.ifc2datetime(
|
||||
rel.TimeLag.LagValue.wrappedValue
|
||||
).days,
|
||||
"lag_time": (
|
||||
0
|
||||
if not rel.TimeLag
|
||||
else ifcopenshell.util.date.ifc2datetime(rel.TimeLag.LagValue.wrappedValue).days
|
||||
),
|
||||
"type": self.sequence_type_map[rel.SequenceType],
|
||||
},
|
||||
)
|
||||
@@ -162,16 +159,20 @@ class Usecase:
|
||||
]
|
||||
)
|
||||
|
||||
predecessor_types = [rel.SequenceType for rel in ifcopenshell.util.sequence.get_sequence_assignment(task, "predecessor")]
|
||||
successor_types = [rel.SequenceType for rel in ifcopenshell.util.sequence.get_sequence_assignment(task, "successor")]
|
||||
predecessor_types = [
|
||||
rel.SequenceType for rel in ifcopenshell.util.sequence.get_sequence_assignment(task, "predecessor")
|
||||
]
|
||||
successor_types = [
|
||||
rel.SequenceType for rel in ifcopenshell.util.sequence.get_sequence_assignment(task, "successor")
|
||||
]
|
||||
|
||||
if not predecessor_types:
|
||||
self.edges.append(("start", task.id(), {"lag_time": 0, "type": "FS"}))
|
||||
if task.TaskTime and task.TaskTime.ScheduleStart:
|
||||
self.start_dates.append(
|
||||
ifcopenshell.util.date.ifc2datetime(task.TaskTime.ScheduleStart)
|
||||
)
|
||||
self.g.nodes[task.id()]["early_start"] = ifcopenshell.util.date.ifc2datetime(task.TaskTime.ScheduleStart) # we assume this task is constrained to start on this date
|
||||
self.start_dates.append(ifcopenshell.util.date.ifc2datetime(task.TaskTime.ScheduleStart))
|
||||
self.g.nodes[task.id()]["early_start"] = ifcopenshell.util.date.ifc2datetime(
|
||||
task.TaskTime.ScheduleStart
|
||||
) # we assume this task is constrained to start on this date
|
||||
if not successor_types:
|
||||
self.edges.append((task.id(), "finish", {"lag_time": 0, "type": "FF"}))
|
||||
|
||||
@@ -188,25 +189,13 @@ class Usecase:
|
||||
self.file,
|
||||
task_time=task.TaskTime,
|
||||
attributes={
|
||||
"FreeFloat": ifcopenshell.util.date.datetime2ifc(
|
||||
data["free_float"], "IfcDuration"
|
||||
),
|
||||
"TotalFloat": ifcopenshell.util.date.datetime2ifc(
|
||||
data["total_float"], "IfcDuration"
|
||||
),
|
||||
"FreeFloat": ifcopenshell.util.date.datetime2ifc(data["free_float"], "IfcDuration"),
|
||||
"TotalFloat": ifcopenshell.util.date.datetime2ifc(data["total_float"], "IfcDuration"),
|
||||
"IsCritical": data["total_float"].days == 0,
|
||||
"EarlyStart": ifcopenshell.util.date.datetime2ifc(
|
||||
data["early_start"], "IfcDateTime"
|
||||
),
|
||||
"EarlyFinish": ifcopenshell.util.date.datetime2ifc(
|
||||
data["early_finish"], "IfcDateTime"
|
||||
),
|
||||
"LateStart": ifcopenshell.util.date.datetime2ifc(
|
||||
data["late_start"], "IfcDateTime"
|
||||
),
|
||||
"LateFinish": ifcopenshell.util.date.datetime2ifc(
|
||||
data["late_finish"], "IfcDateTime"
|
||||
),
|
||||
"EarlyStart": ifcopenshell.util.date.datetime2ifc(data["early_start"], "IfcDateTime"),
|
||||
"EarlyFinish": ifcopenshell.util.date.datetime2ifc(data["early_finish"], "IfcDateTime"),
|
||||
"LateStart": ifcopenshell.util.date.datetime2ifc(data["late_start"], "IfcDateTime"),
|
||||
"LateFinish": ifcopenshell.util.date.datetime2ifc(data["late_finish"], "IfcDateTime"),
|
||||
},
|
||||
)
|
||||
|
||||
@@ -246,11 +235,7 @@ class Usecase:
|
||||
if edge["lag_time"]:
|
||||
days += edge["lag_time"]
|
||||
if days:
|
||||
starts.append(
|
||||
datetime.datetime.combine(
|
||||
self.offset_date(finish, days, data), datetime.time(9)
|
||||
)
|
||||
)
|
||||
starts.append(datetime.datetime.combine(self.offset_date(finish, days, data), datetime.time(9)))
|
||||
starts.append(
|
||||
datetime.datetime.combine(
|
||||
self.offset_date(finish, days, predecessor_data),
|
||||
@@ -265,9 +250,7 @@ class Usecase:
|
||||
return
|
||||
if edge["lag_time"]:
|
||||
starts.append(self.offset_date(start, edge["lag_time"], data))
|
||||
starts.append(
|
||||
self.offset_date(start, edge["lag_time"], predecessor_data)
|
||||
)
|
||||
starts.append(self.offset_date(start, edge["lag_time"], predecessor_data))
|
||||
else:
|
||||
starts.append(start)
|
||||
elif edge["type"] == "FF":
|
||||
@@ -275,12 +258,8 @@ class Usecase:
|
||||
if finish is None:
|
||||
return
|
||||
if edge["lag_time"]:
|
||||
finishes.append(
|
||||
self.offset_date(finish, edge["lag_time"], data)
|
||||
)
|
||||
finishes.append(
|
||||
self.offset_date(finish, edge["lag_time"], predecessor_data)
|
||||
)
|
||||
finishes.append(self.offset_date(finish, edge["lag_time"], data))
|
||||
finishes.append(self.offset_date(finish, edge["lag_time"], predecessor_data))
|
||||
else:
|
||||
finishes.append(finish)
|
||||
elif edge["type"] == "SF":
|
||||
@@ -292,9 +271,7 @@ class Usecase:
|
||||
days += edge["lag_time"]
|
||||
if days or edge["lag_time"]:
|
||||
finishes.append(
|
||||
datetime.datetime.combine(
|
||||
self.offset_date(start, days, data), datetime.time(17)
|
||||
)
|
||||
datetime.datetime.combine(self.offset_date(start, days, data), datetime.time(17))
|
||||
)
|
||||
finishes.append(
|
||||
datetime.datetime.combine(
|
||||
@@ -317,9 +294,7 @@ class Usecase:
|
||||
if potential_finish > data["early_finish"]:
|
||||
data["early_finish"] = potential_finish
|
||||
else:
|
||||
data[
|
||||
"early_start"
|
||||
] = ifcopenshell.util.sequence.get_start_or_finish_date(
|
||||
data["early_start"] = ifcopenshell.util.sequence.get_start_or_finish_date(
|
||||
data["early_finish"],
|
||||
datetime.timedelta(days=data["duration"]),
|
||||
data["duration_type"],
|
||||
@@ -375,9 +350,7 @@ class Usecase:
|
||||
days += edge["lag_time"]
|
||||
if days or edge["lag_time"]:
|
||||
finishes.append(
|
||||
datetime.datetime.combine(
|
||||
self.offset_date(start, -days, data), datetime.time(17)
|
||||
)
|
||||
datetime.datetime.combine(self.offset_date(start, -days, data), datetime.time(17))
|
||||
)
|
||||
finishes.append(
|
||||
datetime.datetime.combine(
|
||||
@@ -402,9 +375,7 @@ class Usecase:
|
||||
return
|
||||
if edge["lag_time"]:
|
||||
starts.append(self.offset_date(start, -edge["lag_time"], data))
|
||||
starts.append(
|
||||
self.offset_date(start, -edge["lag_time"], successor_data)
|
||||
)
|
||||
starts.append(self.offset_date(start, -edge["lag_time"], successor_data))
|
||||
else:
|
||||
starts.append(start)
|
||||
free_floats.append(
|
||||
@@ -421,12 +392,8 @@ class Usecase:
|
||||
if finish is None:
|
||||
return
|
||||
if edge["lag_time"]:
|
||||
finishes.append(
|
||||
self.offset_date(finish, -edge["lag_time"], data)
|
||||
)
|
||||
finishes.append(
|
||||
self.offset_date(finish, -edge["lag_time"], successor_data)
|
||||
)
|
||||
finishes.append(self.offset_date(finish, -edge["lag_time"], data))
|
||||
finishes.append(self.offset_date(finish, -edge["lag_time"], successor_data))
|
||||
else:
|
||||
finishes.append(finish)
|
||||
free_floats.append(
|
||||
@@ -447,9 +414,7 @@ class Usecase:
|
||||
days += edge["lag_time"]
|
||||
if days:
|
||||
starts.append(
|
||||
datetime.datetime.combine(
|
||||
self.offset_date(finish, -days, data), datetime.time(9)
|
||||
)
|
||||
datetime.datetime.combine(self.offset_date(finish, -days, data), datetime.time(9))
|
||||
)
|
||||
starts.append(
|
||||
datetime.datetime.combine(
|
||||
@@ -471,13 +436,8 @@ class Usecase:
|
||||
if starts and finishes:
|
||||
data["late_start"] = min(starts)
|
||||
data["late_finish"] = min(finishes)
|
||||
if (
|
||||
self.offset_date(data["late_start"], data["duration"], data)
|
||||
< data["late_finish"]
|
||||
):
|
||||
data[
|
||||
"late_finish"
|
||||
] = ifcopenshell.util.sequence.get_start_or_finish_date(
|
||||
if self.offset_date(data["late_start"], data["duration"], data) < data["late_finish"]:
|
||||
data["late_finish"] = ifcopenshell.util.sequence.get_start_or_finish_date(
|
||||
data["late_start"],
|
||||
datetime.timedelta(days=data["duration"]),
|
||||
data["duration_type"],
|
||||
@@ -485,9 +445,7 @@ class Usecase:
|
||||
date_type="FINISH",
|
||||
)
|
||||
else:
|
||||
data[
|
||||
"late_start"
|
||||
] = ifcopenshell.util.sequence.get_start_or_finish_date(
|
||||
data["late_start"] = ifcopenshell.util.sequence.get_start_or_finish_date(
|
||||
data["late_finish"],
|
||||
datetime.timedelta(days=data["duration"]),
|
||||
data["duration_type"],
|
||||
@@ -528,9 +486,7 @@ class Usecase:
|
||||
data["total_float"] = data["late_finish"] - data["early_finish"]
|
||||
# If the float is within the span of a single day, it may show as a 8 hours
|
||||
if data["total_float"].seconds == 60 * 60 * 8:
|
||||
data["total_float"] = datetime.timedelta(
|
||||
days=data["total_float"].days + 1
|
||||
)
|
||||
data["total_float"] = datetime.timedelta(days=data["total_float"].days + 1)
|
||||
|
||||
data["free_float"] = min(free_floats) if free_floats else None
|
||||
# If the float is within the span of a single day, it may show as a 8 hours
|
||||
|
||||
Reference in New Issue
Block a user