mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-11 02:02:22 +00:00
Generate functions for all API usecases for better static code features. See #2693.
This commit is contained in:
@@ -21,90 +21,93 @@ import ifcopenshell.util.date
|
||||
import ifcopenshell.util.sequence
|
||||
|
||||
|
||||
def cascade_schedule(file, task=None) -> None:
|
||||
"""Cascades start and end dates of tasks based on durations
|
||||
|
||||
Given a start task with a start date and duration, the end date, and the
|
||||
start and end of all successor tasks with durations may be automatically
|
||||
computed.
|
||||
|
||||
Using this automatic computation is recommended is an alternative to
|
||||
manually specifying dates. It is useful for doing edits and cascading
|
||||
changes.
|
||||
|
||||
Dates can only cascade from predecessor to successors, not backwards.
|
||||
Cyclical relationships are invalid and will result in a recursion error
|
||||
being raised.
|
||||
|
||||
Note that there may be differences between how different planning
|
||||
software calculate start and end dates. Some may consider Monday 5pm to
|
||||
be equivalent to be Tuesday 8am, for instance.
|
||||
|
||||
:param task: The start task to begin cascading from.
|
||||
:type task: ifcopenshell.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# Define a convenience function to add a task chained to a predecessor
|
||||
def add_task(model, name, predecessor, work_schedule):
|
||||
# Add a construction task
|
||||
task = ifcopenshell.api.run("sequence.add_task", model,
|
||||
work_schedule=work_schedule, name=name, predefined_type="CONSTRUCTION")
|
||||
|
||||
# Give it a time
|
||||
task_time = ifcopenshell.api.run("sequence.add_task_time", model, task=task)
|
||||
|
||||
# Arbitrarily set the task's scheduled time duration to be 1 week
|
||||
ifcopenshell.api.run("sequence.edit_task_time", model, task_time=task_time,
|
||||
attributes={"ScheduleStart": datetime.date(2000, 1, 1), "ScheduleDuration": "P1W"})
|
||||
|
||||
# If a predecessor exists, create a finish to start relationship
|
||||
if predecessor:
|
||||
ifcopenshell.api.run("sequence.assign_sequence", model,
|
||||
relating_process=predecessor, related_process=task)
|
||||
|
||||
return task
|
||||
|
||||
# Open an existing IFC4 model you have of a building
|
||||
model = ifcopenshell.open("/path/to/existing/model.ifc")
|
||||
|
||||
# Create a new construction schedule
|
||||
schedule = ifcopenshell.api.run("sequence.add_work_schedule", model, name="Construction")
|
||||
|
||||
# Let's imagine a starting task for site establishment.
|
||||
task = add_task(model, "Site establishment", None, schedule)
|
||||
start_task = task
|
||||
|
||||
# Get all our storeys sorted by elevation ascending.
|
||||
storeys = sorted(model.by_type("IfcBuildingStorey"), key=lambda s: get_storey_elevation(s))
|
||||
|
||||
# For each storey ...
|
||||
for storey in storeys:
|
||||
|
||||
# Add a construction task to construct that storey, using our convenience function
|
||||
task = add_task(model, f"Construct {storey.Name}", task, schedule)
|
||||
|
||||
# Assign all the products in that storey to the task as construction outputs.
|
||||
for product in get_decomposition(storey):
|
||||
ifcopenshell.api.run("sequence.assign_product", model, relating_product=product, related_object=task)
|
||||
|
||||
# Ask the computer to calculate all the dates for us from the start task.
|
||||
# For example, if the first task started on the 1st of January and took a
|
||||
# week, the next task will start on the 8th of January. This saves us
|
||||
# manually doing date calculations.
|
||||
ifcopenshell.api.run("sequence.cascade_schedule", model, task=start_task)
|
||||
|
||||
# Calculate the critical path and floats.
|
||||
ifcopenshell.api.run("sequence.recalculate_schedule", model, work_schedule=schedule)
|
||||
"""
|
||||
usecase = Usecase()
|
||||
usecase.file = file
|
||||
usecase.settings = {"task": task}
|
||||
return usecase.execute()
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, task=None):
|
||||
"""Cascades start and end dates of tasks based on durations
|
||||
|
||||
Given a start task with a start date and duration, the end date, and the
|
||||
start and end of all successor tasks with durations may be automatically
|
||||
computed.
|
||||
|
||||
Using this automatic computation is recommended is an alternative to
|
||||
manually specifying dates. It is useful for doing edits and cascading
|
||||
changes.
|
||||
|
||||
Dates can only cascade from predecessor to successors, not backwards.
|
||||
Cyclical relationships are invalid and will result in a recursion error
|
||||
being raised.
|
||||
|
||||
Note that there may be differences between how different planning
|
||||
software calculate start and end dates. Some may consider Monday 5pm to
|
||||
be equivalent to be Tuesday 8am, for instance.
|
||||
|
||||
:param task: The start task to begin cascading from.
|
||||
:type task: ifcopenshell.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# Define a convenience function to add a task chained to a predecessor
|
||||
def add_task(model, name, predecessor, work_schedule):
|
||||
# Add a construction task
|
||||
task = ifcopenshell.api.run("sequence.add_task", model,
|
||||
work_schedule=work_schedule, name=name, predefined_type="CONSTRUCTION")
|
||||
|
||||
# Give it a time
|
||||
task_time = ifcopenshell.api.run("sequence.add_task_time", model, task=task)
|
||||
|
||||
# Arbitrarily set the task's scheduled time duration to be 1 week
|
||||
ifcopenshell.api.run("sequence.edit_task_time", model, task_time=task_time,
|
||||
attributes={"ScheduleStart": datetime.date(2000, 1, 1), "ScheduleDuration": "P1W"})
|
||||
|
||||
# If a predecessor exists, create a finish to start relationship
|
||||
if predecessor:
|
||||
ifcopenshell.api.run("sequence.assign_sequence", model,
|
||||
relating_process=predecessor, related_process=task)
|
||||
|
||||
return task
|
||||
|
||||
# Open an existing IFC4 model you have of a building
|
||||
model = ifcopenshell.open("/path/to/existing/model.ifc")
|
||||
|
||||
# Create a new construction schedule
|
||||
schedule = ifcopenshell.api.run("sequence.add_work_schedule", model, name="Construction")
|
||||
|
||||
# Let's imagine a starting task for site establishment.
|
||||
task = add_task(model, "Site establishment", None, schedule)
|
||||
start_task = task
|
||||
|
||||
# Get all our storeys sorted by elevation ascending.
|
||||
storeys = sorted(model.by_type("IfcBuildingStorey"), key=lambda s: get_storey_elevation(s))
|
||||
|
||||
# For each storey ...
|
||||
for storey in storeys:
|
||||
|
||||
# Add a construction task to construct that storey, using our convenience function
|
||||
task = add_task(model, f"Construct {storey.Name}", task, schedule)
|
||||
|
||||
# Assign all the products in that storey to the task as construction outputs.
|
||||
for product in get_decomposition(storey):
|
||||
ifcopenshell.api.run("sequence.assign_product", model, relating_product=product, related_object=task)
|
||||
|
||||
# Ask the computer to calculate all the dates for us from the start task.
|
||||
# For example, if the first task started on the 1st of January and took a
|
||||
# week, the next task will start on the 8th of January. This saves us
|
||||
# manually doing date calculations.
|
||||
ifcopenshell.api.run("sequence.cascade_schedule", model, task=start_task)
|
||||
|
||||
# Calculate the critical path and floats.
|
||||
ifcopenshell.api.run("sequence.recalculate_schedule", model, work_schedule=schedule)
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"task": task}
|
||||
|
||||
def execute(self):
|
||||
self.calendar_cache = {}
|
||||
self.cascade_task(self.settings["task"], is_first_task=True)
|
||||
@@ -135,14 +138,10 @@ class Usecase:
|
||||
finishes = []
|
||||
starts = []
|
||||
|
||||
for rel in ifcopenshell.util.sequence.get_sequence_assignment(
|
||||
task, "predecessor"
|
||||
):
|
||||
for rel in ifcopenshell.util.sequence.get_sequence_assignment(task, "predecessor"):
|
||||
predecessor = rel.RelatingProcess
|
||||
predecessor_duration = (
|
||||
ifcopenshell.util.date.ifc2datetime(
|
||||
predecessor.TaskTime.ScheduleDuration
|
||||
)
|
||||
ifcopenshell.util.date.ifc2datetime(predecessor.TaskTime.ScheduleDuration)
|
||||
if predecessor.TaskTime and predecessor.TaskTime.ScheduleDuration
|
||||
else datetime.timedelta()
|
||||
)
|
||||
@@ -154,14 +153,16 @@ class Usecase:
|
||||
duration_type = "WORKTIME"
|
||||
if rel.TimeLag:
|
||||
# updated to handle IfcRatioMeasure as a TimeLag value
|
||||
days += self.get_lag_time_days(rel.TimeLag) if rel.TimeLag.LagValue.is_a("IfcDuration") else predecessor_duration.days * rel.TimeLag.LagValue.wrappedValue
|
||||
days += (
|
||||
self.get_lag_time_days(rel.TimeLag)
|
||||
if rel.TimeLag.LagValue.is_a("IfcDuration")
|
||||
else predecessor_duration.days * rel.TimeLag.LagValue.wrappedValue
|
||||
)
|
||||
duration_type = rel.TimeLag.DurationType
|
||||
if days:
|
||||
starts.append(
|
||||
datetime.datetime.combine(
|
||||
self.offset_date(
|
||||
finish, days, duration_type, self.get_calendar(task)
|
||||
),
|
||||
self.offset_date(finish, days, duration_type, self.get_calendar(task)),
|
||||
datetime.time(9),
|
||||
)
|
||||
)
|
||||
@@ -183,18 +184,14 @@ class Usecase:
|
||||
if not start:
|
||||
continue
|
||||
if rel.TimeLag:
|
||||
days = self.get_lag_time_days(rel.TimeLag) if rel.TimeLag.LagValue.is_a("IfcDuration") else predecessor_duration.days * rel.TimeLag.LagValue.wrappedValue
|
||||
days = (
|
||||
self.get_lag_time_days(rel.TimeLag)
|
||||
if rel.TimeLag.LagValue.is_a("IfcDuration")
|
||||
else predecessor_duration.days * rel.TimeLag.LagValue.wrappedValue
|
||||
)
|
||||
duration_type = rel.TimeLag.DurationType
|
||||
starts.append(
|
||||
self.offset_date(
|
||||
start, days, duration_type, self.get_calendar(task)
|
||||
)
|
||||
)
|
||||
starts.append(
|
||||
self.offset_date(
|
||||
start, days, duration_type, self.get_calendar(predecessor)
|
||||
)
|
||||
)
|
||||
starts.append(self.offset_date(start, days, duration_type, self.get_calendar(task)))
|
||||
starts.append(self.offset_date(start, days, duration_type, self.get_calendar(predecessor)))
|
||||
else:
|
||||
starts.append(start)
|
||||
elif rel.SequenceType == "FINISH_FINISH":
|
||||
@@ -202,18 +199,14 @@ class Usecase:
|
||||
if not finish:
|
||||
continue
|
||||
if rel.TimeLag:
|
||||
days = self.get_lag_time_days(rel.TimeLag) if rel.TimeLag.LagValue.is_a("IfcDuration") else predecessor_duration.days * rel.TimeLag.LagValue.wrappedValue
|
||||
days = (
|
||||
self.get_lag_time_days(rel.TimeLag)
|
||||
if rel.TimeLag.LagValue.is_a("IfcDuration")
|
||||
else predecessor_duration.days * rel.TimeLag.LagValue.wrappedValue
|
||||
)
|
||||
duration_type = rel.TimeLag.DurationType
|
||||
finishes.append(
|
||||
self.offset_date(
|
||||
finish, days, duration_type, self.get_calendar(task)
|
||||
)
|
||||
)
|
||||
finishes.append(
|
||||
self.offset_date(
|
||||
finish, days, duration_type, self.get_calendar(predecessor)
|
||||
)
|
||||
)
|
||||
finishes.append(self.offset_date(finish, days, duration_type, self.get_calendar(task)))
|
||||
finishes.append(self.offset_date(finish, days, duration_type, self.get_calendar(predecessor)))
|
||||
else:
|
||||
finishes.append(finish)
|
||||
elif rel.SequenceType == "START_FINISH":
|
||||
@@ -223,14 +216,16 @@ class Usecase:
|
||||
days = -1
|
||||
duration_type = "WORKTIME"
|
||||
if rel.TimeLag:
|
||||
days += self.get_lag_time_days(rel.TimeLag) if rel.TimeLag.LagValue.is_a("IfcDuration") else predecessor_duration.days * rel.TimeLag.LagValue.wrappedValue
|
||||
days += (
|
||||
self.get_lag_time_days(rel.TimeLag)
|
||||
if rel.TimeLag.LagValue.is_a("IfcDuration")
|
||||
else predecessor_duration.days * rel.TimeLag.LagValue.wrappedValue
|
||||
)
|
||||
duration_type = rel.TimeLag.DurationType
|
||||
if days or rel.TimeLag:
|
||||
finishes.append(
|
||||
datetime.datetime.combine(
|
||||
self.offset_date(
|
||||
start, days, duration_type, self.get_calendar(task)
|
||||
),
|
||||
self.offset_date(start, days, duration_type, self.get_calendar(task)),
|
||||
datetime.time(17),
|
||||
)
|
||||
)
|
||||
@@ -263,9 +258,7 @@ class Usecase:
|
||||
if task.TaskTime.ScheduleStart == start_ifc and not is_first_task:
|
||||
return
|
||||
task.TaskTime.ScheduleStart = start_ifc
|
||||
task.TaskTime.ScheduleFinish = ifcopenshell.util.date.datetime2ifc(
|
||||
potential_finish, "IfcDateTime"
|
||||
)
|
||||
task.TaskTime.ScheduleFinish = ifcopenshell.util.date.datetime2ifc(potential_finish, "IfcDateTime")
|
||||
else:
|
||||
finish_ifc = ifcopenshell.util.date.datetime2ifc(finish, "IfcDateTime")
|
||||
if task.TaskTime.ScheduleFinish == finish_ifc and not is_first_task:
|
||||
@@ -328,15 +321,11 @@ class Usecase:
|
||||
|
||||
def get_calendar(self, task):
|
||||
if task.id() not in self.calendar_cache:
|
||||
self.calendar_cache[task.id()] = ifcopenshell.util.sequence.derive_calendar(
|
||||
task
|
||||
)
|
||||
self.calendar_cache[task.id()] = ifcopenshell.util.sequence.derive_calendar(task)
|
||||
return self.calendar_cache[task.id()]
|
||||
|
||||
def offset_date(self, date, days, duration_type, calendar):
|
||||
return ifcopenshell.util.sequence.offset_date(
|
||||
date, datetime.timedelta(days=days), duration_type, calendar
|
||||
)
|
||||
return ifcopenshell.util.sequence.offset_date(date, datetime.timedelta(days=days), duration_type, calendar)
|
||||
|
||||
def get_task_time_attribute(self, task, attribute):
|
||||
if task.TaskTime:
|
||||
|
||||
Reference in New Issue
Block a user