First Commit
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Manage work schedules, tasks, calendars, and more for 4D
|
||||
|
||||
These are typically used for construction planning, but may also be used in
|
||||
managing recurring facility maintenance schedules.
|
||||
"""
|
||||
|
||||
from .. import wrap_usecases
|
||||
from .add_date_time import add_date_time
|
||||
from .add_task import add_task
|
||||
from .add_task_time import add_task_time
|
||||
from .add_time_period import add_time_period
|
||||
from .add_work_calendar import add_work_calendar
|
||||
from .add_work_plan import add_work_plan
|
||||
from .add_work_schedule import add_work_schedule
|
||||
from .add_work_time import add_work_time
|
||||
from .assign_lag_time import assign_lag_time
|
||||
from .assign_process import assign_process
|
||||
from .assign_product import assign_product
|
||||
from .assign_recurrence_pattern import assign_recurrence_pattern
|
||||
from .assign_sequence import assign_sequence
|
||||
from .assign_work_plan import assign_work_plan
|
||||
from .calculate_task_duration import calculate_task_duration
|
||||
from .cascade_schedule import cascade_schedule
|
||||
from .copy_work_schedule import copy_work_schedule
|
||||
from .create_baseline import create_baseline
|
||||
from .duplicate_task import duplicate_task
|
||||
from .edit_lag_time import edit_lag_time
|
||||
from .edit_recurrence_pattern import edit_recurrence_pattern
|
||||
from .edit_sequence import edit_sequence
|
||||
from .edit_task import edit_task
|
||||
from .edit_task_time import edit_task_time
|
||||
from .edit_work_calendar import edit_work_calendar
|
||||
from .edit_work_plan import edit_work_plan
|
||||
from .edit_work_schedule import edit_work_schedule
|
||||
from .edit_work_time import edit_work_time
|
||||
|
||||
try:
|
||||
from .recalculate_schedule import recalculate_schedule
|
||||
except ModuleNotFoundError as e:
|
||||
print(f"Note: API not available due to missing dependencies: sequence.recalculate_schedule - {e}")
|
||||
from .remove_task import remove_task
|
||||
from .remove_time_period import remove_time_period
|
||||
from .remove_work_calendar import remove_work_calendar
|
||||
from .remove_work_plan import remove_work_plan
|
||||
from .remove_work_schedule import remove_work_schedule
|
||||
from .remove_work_time import remove_work_time
|
||||
from .unassign_lag_time import unassign_lag_time
|
||||
from .unassign_process import unassign_process
|
||||
from .unassign_product import unassign_product
|
||||
from .unassign_recurrence_pattern import unassign_recurrence_pattern
|
||||
from .unassign_sequence import unassign_sequence
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
__all__ = [
|
||||
"add_date_time",
|
||||
"add_task",
|
||||
"add_task_time",
|
||||
"add_time_period",
|
||||
"add_work_calendar",
|
||||
"add_work_plan",
|
||||
"add_work_schedule",
|
||||
"add_work_time",
|
||||
"assign_lag_time",
|
||||
"assign_process",
|
||||
"assign_product",
|
||||
"assign_recurrence_pattern",
|
||||
"assign_sequence",
|
||||
"assign_work_plan",
|
||||
"calculate_task_duration",
|
||||
"cascade_schedule",
|
||||
"copy_work_schedule",
|
||||
"create_baseline",
|
||||
"duplicate_task",
|
||||
"edit_lag_time",
|
||||
"edit_recurrence_pattern",
|
||||
"edit_sequence",
|
||||
"edit_task",
|
||||
"edit_task_time",
|
||||
"edit_work_calendar",
|
||||
"edit_work_plan",
|
||||
"edit_work_schedule",
|
||||
"edit_work_time",
|
||||
"recalculate_schedule",
|
||||
"remove_task",
|
||||
"remove_time_period",
|
||||
"remove_work_calendar",
|
||||
"remove_work_plan",
|
||||
"remove_work_schedule",
|
||||
"remove_work_time",
|
||||
"unassign_lag_time",
|
||||
"unassign_process",
|
||||
"unassign_product",
|
||||
"unassign_recurrence_pattern",
|
||||
"unassign_sequence",
|
||||
]
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1,60 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Union
|
||||
|
||||
import ifcopenshell.util.date
|
||||
|
||||
|
||||
def add_date_time(file: ifcopenshell.file, dt: datetime) -> Union[str, ifcopenshell.entity_instance]:
|
||||
"""Add a new date time.
|
||||
|
||||
Depending on ``file``'s schema method will:
|
||||
- IFC2X3 - create IfcDateAndTime entity
|
||||
- IFC4+ - create IfcDatetime formatted string
|
||||
|
||||
:param dt: datetime to convert to IFC.
|
||||
:return: IfcDateAndTime entity or IfcDatetime string.
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
dt = datetime(2025, 3, 1, 12, 31, 24)
|
||||
datetime_ifc = ifcopenshell.api.sequence.add_date_time(self.file, dt)
|
||||
|
||||
# IFC2X3: #1=IfcDateAndTime(#2,#3)
|
||||
# IFC4+: "2025-03-01T12:31:24"
|
||||
print(datetime_ifc)
|
||||
|
||||
"""
|
||||
|
||||
if file.schema == "IFC2X3":
|
||||
ifc_dt = file.create_entity("IfcDateAndTime")
|
||||
calendar_date_data = ifcopenshell.util.date.datetime2ifc(dt, "IfcCalendarDate")
|
||||
assert isinstance(calendar_date_data, dict)
|
||||
ifc_dt.DateComponent = file.create_entity("IfcCalendarDate", **calendar_date_data)
|
||||
local_time_data = ifcopenshell.util.date.datetime2ifc(dt, "IfcLocalTime")
|
||||
assert isinstance(local_time_data, dict)
|
||||
ifc_dt.TimeComponent = file.create_entity("IfcLocalTime", **local_time_data)
|
||||
return ifc_dt
|
||||
|
||||
dt_str = ifcopenshell.util.date.datetime2ifc(dt, "IfcDateTime")
|
||||
assert isinstance(dt_str, str)
|
||||
return dt_str
|
||||
@@ -0,0 +1,152 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from typing import Optional
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.control
|
||||
import ifcopenshell.api.nest
|
||||
import ifcopenshell.api.root
|
||||
|
||||
|
||||
def add_task(
|
||||
file: ifcopenshell.file,
|
||||
work_schedule: Optional[ifcopenshell.entity_instance] = None,
|
||||
parent_task: Optional[ifcopenshell.entity_instance] = None,
|
||||
name: Optional[str] = None,
|
||||
description: Optional[str] = None,
|
||||
identification: Optional[str] = None,
|
||||
predefined_type: str = "NOTDEFINED",
|
||||
) -> ifcopenshell.entity_instance:
|
||||
"""Adds a new task
|
||||
|
||||
Tasks are typically used for two purposes: construction scheduling and
|
||||
facility management.
|
||||
|
||||
In construction scheduling, a task represents a job to be done in a work
|
||||
schedule. Tasks are organised in a hierarchical manner known as a work
|
||||
breakdown structure (WBS) and have lots of sequential relationships
|
||||
(e.g. this task must finish before the next task can start) and date
|
||||
information (e.g. durations, start dates). This is often represented as
|
||||
a gantt chart and used to analyse critical paths to try and reduce
|
||||
project time to stay on-time and within budget.
|
||||
|
||||
In facility management, a task represents a maintenance task to maintain
|
||||
a piece of equipment. Tasks are broken down into a punch list, or simply
|
||||
a bulleted or ordered sequence of tasks to be performed (e.g. turn off
|
||||
equipment, check power connection, etc) in order to maintain the
|
||||
equipment. Tasks will also typically have recurring scheduled dates in
|
||||
line with the maintenance schedule. These maintenance tasks and
|
||||
procedures are typically published as part of an operations and
|
||||
maintenance manual.
|
||||
|
||||
All tasks must be grouped in a work schedule, either directly as a root
|
||||
or top-level task, or indirectly as a child or subtask of a parent task.
|
||||
In construction scheduling, tasks may be nested many times to create the
|
||||
work breakdown structure, and the "leaf" tasks (i.e. tasks with no more
|
||||
subtasks) are considered to be the activities with dates, whereas all
|
||||
parent tasks are part of the breakdown structure used for categorisation
|
||||
purposes. In facility management, top-level tasks represent the overall
|
||||
maintenance job to be performed, and child tasks represent an ordered
|
||||
list of things to do for that maintenance. These form a 2-level
|
||||
hierarchy. No further child tasks are recommended.
|
||||
|
||||
:param work_schedule: The work schedule to group the task in, if the
|
||||
task is to be a top-level or root task. This is mutually exclusive
|
||||
with the parent_task parameter.
|
||||
:param parent_task: The parent task, if the task is to be a subtask or
|
||||
child task. This is mutually exclusive with the work_schedule
|
||||
parameter.
|
||||
:param name: The name of the task.
|
||||
:param description: The description of the task.
|
||||
:param identification: The identification code of the task.
|
||||
:param predefined_type: The predefined type of the task. Common ones
|
||||
include CONSTRUCTION, DEMOLITION, or MAINTENANCE. Consultant the
|
||||
IFC documentation for IfcTaskTypeEnum for more information.
|
||||
:return: The newly created IfcTask
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# Let's imagine we are creating a construction schedule. All tasks
|
||||
# need to be part of a work schedule.
|
||||
schedule = ifcopenshell.api.sequence.add_work_schedule(model, name="Construction Schedule A")
|
||||
|
||||
# Add a root task to represent the design milestones, and major
|
||||
# project phases.
|
||||
ifcopenshell.api.sequence.add_task(model,
|
||||
work_schedule=schedule, name="Milestones", identification="A")
|
||||
ifcopenshell.api.sequence.add_task(model,
|
||||
work_schedule=schedule, name="Design", identification="B")
|
||||
construction = ifcopenshell.api.sequence.add_task(model,
|
||||
work_schedule=schedule, name="Construction", identification="C")
|
||||
|
||||
# Let's start creating our work breakdown structure.
|
||||
ifcopenshell.api.sequence.add_task(model,
|
||||
parent_task=construction, name="Early Works", identification="C1")
|
||||
ifcopenshell.api.sequence.add_task(model,
|
||||
parent_task=construction, name="Substructure", identification="C2")
|
||||
superstructure = ifcopenshell.api.sequence.add_task(model,
|
||||
parent_task=construction, name="Superstructure", identification="C3")
|
||||
|
||||
# Notice how the leaf task is the actual activity
|
||||
ifcopenshell.api.sequence.add_task(model,
|
||||
parent_task=superstructure, name="Ground Floor FRP", identification="C3.1")
|
||||
|
||||
# Let's imagine we are digitising an operations and maintenance
|
||||
# manual for the mechanical discipline.
|
||||
maintenance = ifcopenshell.api.sequence.add_work_schedule(model, name="Mechanical Maintenance")
|
||||
|
||||
# Imagine we have to clean the condenser coils for a chiller every
|
||||
# month. Like the schedule above, to keep things simple we won't
|
||||
# show scheduling times and calendars. This root task represents the
|
||||
# overall maintenance task.
|
||||
cleaning = ifcopenshell.api.sequence.add_task(model,
|
||||
work_schedule=maintenance, name="Condenser coil cleaning")
|
||||
|
||||
# These subtasks represent the punch list of maintenance tasks.
|
||||
ifcopenshell.api.sequence.add_task(model, parent_task=cleaning, identification="1",
|
||||
description="Prior to work, wear safety shoes, gloves, and goggles.")
|
||||
ifcopenshell.api.sequence.add_task(model, parent_task=cleaning, identification="2",
|
||||
description="Prepare jet pump, screwdriver, hose clamp, and control panel door key.")
|
||||
ifcopenshell.api.sequence.add_task(model, parent_task=cleaning, identification="3",
|
||||
description="Switch OFF the chiller unit.")
|
||||
ifcopenshell.api.sequence.add_task(model, parent_task=cleaning, identification="3",
|
||||
description="Open the isolator switch.")
|
||||
ifcopenshell.api.sequence.add_task(model, parent_task=cleaning, identification="3",
|
||||
description="Setup the water pressure by tapping to a water supply and connecting to a ...")
|
||||
"""
|
||||
task = ifcopenshell.api.root.create_entity(file, ifc_class="IfcTask", name=name, predefined_type=predefined_type)
|
||||
if description:
|
||||
task.Description = description
|
||||
if identification:
|
||||
task.Identification = identification
|
||||
task.IsMilestone = False
|
||||
if work_schedule:
|
||||
ifcopenshell.api.control.assign_control(file, work_schedule, [task])
|
||||
elif parent_task:
|
||||
rel = ifcopenshell.api.nest.assign_object(
|
||||
file,
|
||||
related_objects=[task],
|
||||
relating_object=parent_task,
|
||||
)
|
||||
if file.schema != "IFC2X3" and parent_task.Identification:
|
||||
assert rel
|
||||
task.Identification = parent_task.Identification + "." + str(len(rel.RelatedObjects))
|
||||
return task
|
||||
@@ -0,0 +1,66 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
import ifcopenshell
|
||||
|
||||
|
||||
def add_task_time(
|
||||
file: ifcopenshell.file, task: ifcopenshell.entity_instance, is_recurring: bool = False
|
||||
) -> ifcopenshell.entity_instance:
|
||||
"""Adds a task time to a task
|
||||
|
||||
Some tasks, such as activities within a work breakdown structure or
|
||||
overall maintenance tasks will have time related information. This
|
||||
includes start dates, durations, end dates, and possible recurring times
|
||||
(especially for maintenance tasks).
|
||||
|
||||
:param task: The task to add time data to.
|
||||
:param is_recurring: Whether or not the time should recur.
|
||||
:return: The newly created IfcTaskTime.
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# Let's imagine we are creating a construction schedule.
|
||||
schedule = ifcopenshell.api.sequence.add_work_schedule(model, name="Construction Schedule A")
|
||||
|
||||
# Create a portion of a work breakdown structure.
|
||||
construction = ifcopenshell.api.sequence.add_task(model,
|
||||
work_schedule=schedule, name="Construction", identification="C")
|
||||
superstructure = ifcopenshell.api.sequence.add_task(model,
|
||||
parent_task=construction, name="Superstructure", identification="C3")
|
||||
task = ifcopenshell.api.sequence.add_task(model,
|
||||
parent_task=superstructure, name="Ground Floor FRP", identification="C3.1")
|
||||
|
||||
# Add time data. Note that time data is blank by default.
|
||||
time = ifcopenshell.api.sequence.add_task_time(model, task=task)
|
||||
|
||||
# Let's say our task starts on the first of January when everybody
|
||||
# is still drunk from the new years celebration, and lasts for 2
|
||||
# days. Note we don't need to specify the end date, as that is
|
||||
# derived from the start plus the duration. In this simple example,
|
||||
# no calendar has been specified, so we are working 24/7. Yikes!
|
||||
ifcopenshell.api.sequence.edit_task_time(model,
|
||||
task_time=time, attributes={"ScheduleStart": "2000-01-01", "ScheduleDuration": "P2D"})
|
||||
"""
|
||||
if is_recurring:
|
||||
task_time = file.create_entity("IfcTaskTimeRecurring")
|
||||
else:
|
||||
task_time = file.create_entity("IfcTaskTime")
|
||||
task.TaskTime = task_time
|
||||
return task_time
|
||||
@@ -0,0 +1,90 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from datetime import time
|
||||
from typing import Optional, Union
|
||||
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.date
|
||||
import ifcopenshell.util.sequence
|
||||
|
||||
|
||||
def add_time_period(
|
||||
file: ifcopenshell.file,
|
||||
recurrence_pattern: ifcopenshell.entity_instance,
|
||||
start_time: Optional[Union[str, time]] = None,
|
||||
end_time: Optional[Union[str, time]] = None,
|
||||
) -> ifcopenshell.entity_instance:
|
||||
"""Adds a time period to a recurrence pattern
|
||||
|
||||
A recurring time may be an all-day event, or only during certain time
|
||||
periods of the day. For example, you might say that every 1st of January
|
||||
recurring is a public holiday, which is an all-day event. Alternatively,
|
||||
you might say that you work every (i.e. recurringly) Monday to Friday,
|
||||
from 9am to 5pm. The 9am to 5pm is the time period.
|
||||
|
||||
There may also be multiple recurrence patterns, such as from 9am to
|
||||
12pm, and then another from 1pm to 5pm (to indicate an hour break for
|
||||
lunch).
|
||||
|
||||
:param recurrence_pattern: The IfcRecurrencePattern to add the time
|
||||
period to. See ifcopenshell.api.sequence.assign_recurrence_pattern.
|
||||
:param start_time: The start time of the time period, in a format
|
||||
compatible with IfcTime, such as an ISO format time string or a
|
||||
datetime.time object.
|
||||
:param end_time: The end time of the time period, in a format
|
||||
compatible with IfcTime, such as an ISO format time string or a
|
||||
datetime.time object.
|
||||
:return: The newly created IfcTimePeriod
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# Let's create a new calendar.
|
||||
calendar = ifcopenshell.api.sequence.add_work_calendar(model)
|
||||
|
||||
# Let's start defining the times that we work during the week.
|
||||
work_time = ifcopenshell.api.sequence.add_work_time(model,
|
||||
work_calendar=calendar, time_type="WorkingTimes")
|
||||
|
||||
# We create a weekly recurrence
|
||||
pattern = ifcopenshell.api.sequence.assign_recurrence_pattern(model,
|
||||
parent=work_time, recurrence_type="WEEKLY")
|
||||
|
||||
# State that we work from weekdays 1 to 5 (i.e. Monday to Friday)
|
||||
ifcopenshell.api.sequence.edit_recurrence_pattern(model,
|
||||
recurrence_pattern=pattern, attributes={"WeekdayComponent": [1, 2, 3, 4, 5]})
|
||||
|
||||
# The morning work session, lunch, then the afternoon work session.
|
||||
ifcopenshell.api.sequence.add_time_period(model,
|
||||
recurrence_pattern=pattern, start_time="09:00", end_time="12:00")
|
||||
ifcopenshell.api.sequence.add_time_period(model,
|
||||
recurrence_pattern=pattern, start_time="13:00", end_time="17:00")
|
||||
"""
|
||||
time_period = file.create_entity("IfcTimePeriod")
|
||||
time_period.StartTime = ifcopenshell.util.date.datetime2ifc(start_time, "IfcTime")
|
||||
time_period.EndTime = ifcopenshell.util.date.datetime2ifc(end_time, "IfcTime")
|
||||
time_periods = list(recurrence_pattern.TimePeriods or [])
|
||||
time_periods.append(time_period)
|
||||
recurrence_pattern.TimePeriods = time_periods
|
||||
|
||||
ifcopenshell.util.sequence.is_working_day.cache_clear()
|
||||
ifcopenshell.util.sequence.is_calendar_applicable.cache_clear()
|
||||
|
||||
return time_period
|
||||
@@ -0,0 +1,92 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell.api.project
|
||||
import ifcopenshell.api.root
|
||||
|
||||
|
||||
def add_work_calendar(
|
||||
file: ifcopenshell.file, name: str = "Unnamed", predefined_type: str = "NOTDEFINED"
|
||||
) -> ifcopenshell.entity_instance:
|
||||
"""Add a work calendar
|
||||
|
||||
A work calendar defines when work is allowed to occur and when the
|
||||
holidays are. This is a fundamental concept in construction planning.
|
||||
Every task in a work schedule will have an associated calendar. Some
|
||||
task and resources work 24/7, whereas others work Monday to Friday, or
|
||||
5.5 day weeks, etc. This is important, as tasks durations may only occur
|
||||
during working times in a work calendar.
|
||||
|
||||
Work calendars can also be used to associate with events, such as
|
||||
indicating that during certain days and times of the year, motion
|
||||
sensors should turn on the lights, and other smart building controls.
|
||||
|
||||
:param name: The name of the calendar. Typically something like
|
||||
"5 Day Working Week" or "24/7".
|
||||
:param predefined_type: The type of calendar, typically used to more
|
||||
specifically define shifts, such as FIRSTSHIFT, SECONDSHIFT, or
|
||||
THIRDSHIFT. Leave as NOTDEFINED for basic calendar usage.
|
||||
:return: The newly created IfcWorkCalendar
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# Let's imagine we are creating a construction schedule. All tasks
|
||||
# need to be part of a work schedule.
|
||||
schedule = ifcopenshell.api.sequence.add_work_schedule(model, name="Construction Schedule A")
|
||||
|
||||
# Add a root task to represent the construction tasks.
|
||||
task = ifcopenshell.api.sequence.add_task(model,
|
||||
work_schedule=schedule, name="Construction", identification="C")
|
||||
|
||||
# Let's create a new calendar.
|
||||
calendar = ifcopenshell.api.sequence.add_work_calendar(model, name="5 Day Week")
|
||||
|
||||
# Let's start defining the times that we work during the week.
|
||||
work_time = ifcopenshell.api.sequence.add_work_time(model,
|
||||
work_calendar=calendar, time_type="WorkingTimes")
|
||||
|
||||
# We create a weekly recurrence
|
||||
pattern = ifcopenshell.api.sequence.assign_recurrence_pattern(model,
|
||||
parent=work_time, recurrence_type="WEEKLY")
|
||||
|
||||
# State that we work from weekdays 1 to 5 (i.e. Monday to Friday), 9am to 5pm
|
||||
ifcopenshell.api.sequence.edit_recurrence_pattern(model,
|
||||
recurrence_pattern=pattern, attributes={"WeekdayComponent": [1, 2, 3, 4, 5]})
|
||||
ifcopenshell.api.sequence.add_time_period(model,
|
||||
recurrence_pattern=pattern, start_time="09:00", end_time="17:00")
|
||||
|
||||
# We associate the calendar with the construction root task. All
|
||||
# subtasks underneath the construction work task will also inherit
|
||||
# this calendar by default (though you can override them).
|
||||
ifcopenshell.api.control.assign_control(model, relating_control=calendar, related_objects=[task])
|
||||
"""
|
||||
work_calendar = ifcopenshell.api.root.create_entity(
|
||||
file,
|
||||
ifc_class="IfcWorkCalendar",
|
||||
predefined_type=predefined_type,
|
||||
name=name,
|
||||
)
|
||||
context = file.by_type("IfcContext")[0]
|
||||
ifcopenshell.api.project.assign_declaration(
|
||||
file,
|
||||
definitions=[work_calendar],
|
||||
relating_context=context,
|
||||
)
|
||||
return work_calendar
|
||||
@@ -0,0 +1,82 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from datetime import datetime, time
|
||||
from typing import Optional, Union
|
||||
|
||||
import ifcopenshell.api.owner.settings
|
||||
import ifcopenshell.api.project
|
||||
import ifcopenshell.api.root
|
||||
import ifcopenshell.api.sequence
|
||||
|
||||
|
||||
def add_work_plan(
|
||||
file: ifcopenshell.file,
|
||||
name: Optional[str] = None,
|
||||
predefined_type: str = "NOTDEFINED",
|
||||
start_time: Optional[Union[str, time]] = None,
|
||||
) -> ifcopenshell.entity_instance:
|
||||
"""Add a new work plan
|
||||
|
||||
A work plan is a group of work schedules. Since work schedules may have
|
||||
different purposes, such as for maintenance or construction scheduling,
|
||||
baseline comparison, or phasing, work plans can be used to group related
|
||||
work schedules. At a minimum, it is recommended to use work plans to
|
||||
indicate whether the work schedules are for facility management or for
|
||||
construction scheduling.
|
||||
|
||||
:param name: The name of the work plan. Recommended to be "Maintenance"
|
||||
or "Construction" for the two main purposes.
|
||||
:param predefined_type: The type of work plan, used for baselining.
|
||||
Leave as "NOTDEFINED" if unsure.
|
||||
:param start_time: The earliest start time when the schedules grouped
|
||||
within the work plan are relevant.
|
||||
:return: The newly created IfcWorkPlan
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# This will hold all our construction schedules
|
||||
work_plan = ifcopenshell.api.sequence.add_work_plan(model, name="Construction")
|
||||
|
||||
# This is one of our schedules in our work plan.
|
||||
schedule = ifcopenshell.api.sequence.add_work_schedule(model,
|
||||
name="Construction Schedule A", work_plan=work_plan)
|
||||
"""
|
||||
start_time = start_time or datetime.now()
|
||||
work_plan = ifcopenshell.api.root.create_entity(
|
||||
file,
|
||||
ifc_class="IfcWorkPlan",
|
||||
predefined_type=predefined_type,
|
||||
name=name,
|
||||
)
|
||||
work_plan.CreationDate = ifcopenshell.api.sequence.add_date_time(file, datetime.now())
|
||||
user = ifcopenshell.api.owner.settings.get_user(file)
|
||||
if user:
|
||||
work_plan.Creators = [user.ThePerson]
|
||||
work_plan.StartTime = ifcopenshell.api.sequence.add_date_time(file, datetime.now())
|
||||
|
||||
if file.schema != "IFC2X3":
|
||||
context = file.by_type("IfcContext")[0]
|
||||
ifcopenshell.api.project.assign_declaration(
|
||||
file,
|
||||
definitions=[work_plan],
|
||||
relating_context=context,
|
||||
)
|
||||
return work_plan
|
||||
@@ -0,0 +1,106 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from datetime import datetime, time
|
||||
from typing import Optional, Union
|
||||
|
||||
import ifcopenshell.api.aggregate
|
||||
import ifcopenshell.api.owner.settings
|
||||
import ifcopenshell.api.project
|
||||
import ifcopenshell.api.root
|
||||
import ifcopenshell.api.sequence
|
||||
|
||||
|
||||
def add_work_schedule(
|
||||
file: ifcopenshell.file,
|
||||
name: str = "Unnamed",
|
||||
predefined_type: str = "NOTDEFINED",
|
||||
object_type: Union[str, None] = None,
|
||||
start_time: Optional[Union[str, time]] = None,
|
||||
work_plan: Optional[ifcopenshell.entity_instance] = None,
|
||||
) -> ifcopenshell.entity_instance:
|
||||
"""Add a new work schedule
|
||||
|
||||
A work schedule is a group of tasks, where the tasks are typically
|
||||
either for maintenance or for construction scheduling.
|
||||
|
||||
:param name: The name of the work schedule.
|
||||
:param predefined_type: The type of schedule, chosen from ACTUAL,
|
||||
BASELINE, and PLANNED. Typically you would start with PLANNED, then
|
||||
convert to a BASELINE when changes are made with separate schedules,
|
||||
then have a parallel ACTUAL schedule.
|
||||
:param object_type: Work schedule Object Type. Should be provided
|
||||
in case if ``predefined_type`` is USERDEFINED.
|
||||
:param start_time: The earlier start time when the schedule is relevant.
|
||||
May be represented with an ISO standard string.
|
||||
:param work_plan: The IfcWorkPlan the schedule will be part of. If not
|
||||
provided, the schedule will not be grouped in a work plan and would
|
||||
exist as a top level schedule in the project. This is not
|
||||
recommended.
|
||||
:return: The newly created IfcWorkSchedule
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# This will hold all our construction schedules
|
||||
work_plan = ifcopenshell.api.sequence.add_work_plan(model, name="Construction")
|
||||
|
||||
# Let's imagine this is one of our schedules in our work plan.
|
||||
schedule = ifcopenshell.api.sequence.add_work_schedule(model,
|
||||
name="Construction Schedule A", work_plan=work_plan)
|
||||
|
||||
# Add a root task to represent the design milestones, and major
|
||||
# project phases.
|
||||
ifcopenshell.api.sequence.add_task(model,
|
||||
work_schedule=schedule, name="Milestones", identification="A")
|
||||
ifcopenshell.api.sequence.add_task(model,
|
||||
work_schedule=schedule, name="Design", identification="B")
|
||||
construction = ifcopenshell.api.sequence.add_task(model,
|
||||
work_schedule=schedule, name="Construction", identification="C")
|
||||
"""
|
||||
start_time = start_time or datetime.now()
|
||||
work_schedule = ifcopenshell.api.root.create_entity(
|
||||
file,
|
||||
ifc_class="IfcWorkSchedule",
|
||||
predefined_type=predefined_type,
|
||||
name=name,
|
||||
)
|
||||
work_schedule.CreationDate = ifcopenshell.api.sequence.add_date_time(file, datetime.now())
|
||||
user = ifcopenshell.api.owner.settings.get_user(file)
|
||||
if user:
|
||||
work_schedule.Creators = [user.ThePerson]
|
||||
work_schedule.StartTime = ifcopenshell.api.sequence.add_date_time(file, start_time)
|
||||
if object_type:
|
||||
work_schedule.ObjectType = object_type
|
||||
if work_plan:
|
||||
ifcopenshell.api.aggregate.assign_object(
|
||||
file,
|
||||
products=[work_schedule],
|
||||
relating_object=work_plan,
|
||||
)
|
||||
elif file.schema != "IFC2X3":
|
||||
# TODO: this is an ambiguity by buildingSMART
|
||||
# See https://forums.buildingsmart.org/t/is-the-ifcworkschedule-project-declaration-mutually-exclusive-to-aggregation-within-a-relating-ifcworkplan/3510
|
||||
context = file.by_type("IfcContext")[0]
|
||||
ifcopenshell.api.project.assign_declaration(
|
||||
file,
|
||||
definitions=[work_schedule],
|
||||
relating_context=context,
|
||||
)
|
||||
return work_schedule
|
||||
@@ -0,0 +1,84 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
from typing import Literal
|
||||
|
||||
import ifcopenshell
|
||||
|
||||
TIME_TYPE = Literal["WorkingTimes", "ExceptionTimes"]
|
||||
|
||||
|
||||
def add_work_time(
|
||||
file: ifcopenshell.file, work_calendar: ifcopenshell.entity_instance, time_type: TIME_TYPE = "WorkingTimes"
|
||||
) -> ifcopenshell.entity_instance:
|
||||
"""Add either working times or holiday times to a calendar
|
||||
|
||||
A calendar defines when work occurs by defining working times and
|
||||
holiday times. First, the working times are defined, then the holidays
|
||||
may override the working times. For this reason, holidays are also known
|
||||
as exception times. For example, you might define the working times as
|
||||
every Monday to Friday, then define a few holidays in the year, such as
|
||||
the 1st of January. If the 1st of January is on a weekday, it will
|
||||
override the work time.
|
||||
|
||||
:param work_calendar: The IfcWorkCalendar to add the work or holiday
|
||||
time definition to.
|
||||
:param time_type: Either WorkingTimes or ExceptionTimes, depending on
|
||||
what you want to define.
|
||||
:return: The newly created IfcWorkTime
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# Let's create a new calendar.
|
||||
calendar = ifcopenshell.api.sequence.add_work_calendar(model)
|
||||
|
||||
# Let's start defining the times that we work during the week.
|
||||
work_time = ifcopenshell.api.sequence.add_work_time(model,
|
||||
work_calendar=calendar, time_type="WorkingTimes")
|
||||
|
||||
# We create a weekly recurrence
|
||||
pattern = ifcopenshell.api.sequence.assign_recurrence_pattern(model,
|
||||
parent=work_time, recurrence_type="WEEKLY")
|
||||
|
||||
# State that we work from weekdays 1 to 5 (i.e. Monday to Friday)
|
||||
ifcopenshell.api.sequence.edit_recurrence_pattern(model,
|
||||
recurrence_pattern=pattern, attributes={"WeekdayComponent": [1, 2, 3, 4, 5]})
|
||||
|
||||
# Let's set some holidays
|
||||
holidays = ifcopenshell.api.sequence.add_work_time(model,
|
||||
work_calendar=calendar, time_type="ExceptionTimes")
|
||||
|
||||
# We create a yearly recurrence
|
||||
pattern = ifcopenshell.api.sequence.assign_recurrence_pattern(model,
|
||||
parent=work_time, recurrence_type="YEARLY_BY_DAY_OF_MONTH")
|
||||
|
||||
# The holiday is every 1st of January
|
||||
ifcopenshell.api.sequence.edit_recurrence_pattern(model,
|
||||
recurrence_pattern=pattern, attributes={"DayComponent": [1], "MonthComponent": [1]})
|
||||
"""
|
||||
work_time = file.create_entity("IfcWorkTime")
|
||||
if time_type == "WorkingTimes":
|
||||
working_times = list(work_calendar.WorkingTimes or [])
|
||||
working_times.append(work_time)
|
||||
work_calendar.WorkingTimes = working_times
|
||||
elif time_type == "ExceptionTimes":
|
||||
exception_times = list(work_calendar.ExceptionTimes or [])
|
||||
exception_times.append(work_time)
|
||||
work_calendar.ExceptionTimes = exception_times
|
||||
return work_time
|
||||
@@ -0,0 +1,89 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell.util.date
|
||||
|
||||
|
||||
def assign_lag_time(
|
||||
file: ifcopenshell.file, rel_sequence: ifcopenshell.entity_instance, lag_value: str, duration_type: str = "WORKTIME"
|
||||
) -> ifcopenshell.entity_instance:
|
||||
"""Assign a lag time to a sequence relationship between tasks
|
||||
|
||||
A task sequence (e.g. finish to start) may optionally have a lag time
|
||||
defined. This is a fundamental concept in construction scheduling. The
|
||||
lag is defined as a duration, and the duration is typically either
|
||||
calendar based (i.e. follows the working times and holidays of the
|
||||
calendar) or elapsed time based (i.e. 24/7).
|
||||
|
||||
A sequence may only have a single lag time defined. Negative lag times
|
||||
are allowed.
|
||||
|
||||
:param rel_sequence: The IfcRelSequence to assign the lag time to.
|
||||
:param lag_value: An ISO standardised duration string.
|
||||
:param duration_type: Choose from WORKTIME for the associated
|
||||
calendar-based lag times (this is the most common scenario and is
|
||||
recommended as a default), or ELAPSEDTIME to not follow the
|
||||
calendar. You may also choose NOTDEFINED but the behaviour of this
|
||||
is unclear.
|
||||
:return: The newly created IfcLagTime
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# Let's imagine we are creating a construction schedule. All tasks
|
||||
# need to be part of a work schedule.
|
||||
schedule = ifcopenshell.api.sequence.add_work_schedule(model, name="Construction Schedule A")
|
||||
|
||||
# Let's imagine a root construction task
|
||||
construction = ifcopenshell.api.sequence.add_task(model,
|
||||
work_schedule=schedule, name="Construction", identification="C")
|
||||
|
||||
# Let's imagine we're doing a typically formwork, reinforcement,
|
||||
# pour sequence. Let's start with the formwork. It'll take us 2
|
||||
# days.
|
||||
formwork = ifcopenshell.api.sequence.add_task(model,
|
||||
parent_task=construction, name="Formwork", identification="C.1")
|
||||
time = ifcopenshell.api.sequence.add_task_time(model, task=formwork)
|
||||
ifcopenshell.api.sequence.edit_task_time(model,
|
||||
task_time=time, attributes={"ScheduleStart": "2000-01-01", "ScheduleDuration": "P2D"})
|
||||
|
||||
# Now let's do the reinforcement. It'll take us another 2 days.
|
||||
reinforcement = ifcopenshell.api.sequence.add_task(model,
|
||||
parent_task=construction, name="Reinforcement", identification="C.2")
|
||||
time = ifcopenshell.api.sequence.add_task_time(model, task=reinforcement)
|
||||
ifcopenshell.api.sequence.edit_task_time(model,
|
||||
task_time=time, attributes={"ScheduleStart": "2000-01-01", "ScheduleDuration": "P2D"})
|
||||
|
||||
# Now let's say the formwork must finish before the reinforcement
|
||||
# can start. This is a typical finish to start relationship (FS).
|
||||
sequence = ifcopenshell.api.sequence.assign_sequence(model,
|
||||
relating_process=formwork, related_process=reinforcement)
|
||||
|
||||
# Now typically there would be no lag time between formwork and
|
||||
# reinforcement, but let's pretend that we had to allow 1 day gap
|
||||
# for whatever reason.
|
||||
ifcopenshell.api.sequence.assign_lag_time(model, rel_sequence=sequence, lag_value="P1D")
|
||||
"""
|
||||
duration = file.create_entity("IfcDuration", ifcopenshell.util.date.datetime2ifc(lag_value, "IfcDuration"))
|
||||
lag_time = file.create_entity("IfcLagTime", DurationType=duration_type, LagValue=duration)
|
||||
if rel_sequence.is_a("IfcRelSequence"):
|
||||
if (current_lag_time := rel_sequence.TimeLag) and file.get_total_inverses(current_lag_time) == 1:
|
||||
file.remove(current_lag_time)
|
||||
rel_sequence.TimeLag = lag_time
|
||||
return lag_time
|
||||
@@ -0,0 +1,130 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.owner
|
||||
import ifcopenshell.guid
|
||||
|
||||
|
||||
def assign_process(
|
||||
file: ifcopenshell.file,
|
||||
relating_process: ifcopenshell.entity_instance,
|
||||
related_object: ifcopenshell.entity_instance,
|
||||
) -> ifcopenshell.entity_instance:
|
||||
"""Assigns an object as an input, control, or resource of a process
|
||||
|
||||
Processes work using the ICOM (Input, Controls, Outputs, Mechanisms)
|
||||
paradigm in IFC. This process model is commonly used in modeling
|
||||
manufacturing functions.
|
||||
|
||||
For example, processes (such as tasks) consume Inputs and transform them
|
||||
into Outputs. The process may only occur within the limits of Controls
|
||||
(e.g. cost items) and may require Mechanisms (ISO9000 calls them
|
||||
Mechanisms, whereas IFC calls them resources, such as raw materials,
|
||||
labour, or equipment).
|
||||
|
||||
+----------+
|
||||
| Controls |
|
||||
+----------+
|
||||
|
|
||||
V
|
||||
+--------+ +---------+ +---------+
|
||||
| Inputs | --> | Process | --> | Outputs |
|
||||
+--------+ +---------+ +---------+
|
||||
^
|
||||
|
|
||||
+-----------+
|
||||
| Resources |
|
||||
+-----------+
|
||||
|
||||
There are three main scenarios where an object may be related to a
|
||||
task: defining inputs, controls, and resources of a process.
|
||||
|
||||
For inputs, a product (i.e. wall) may be defined as an input to a task,
|
||||
such as when the task is to demolish the wall (i.e. the wall is an
|
||||
input, and there is no output).
|
||||
|
||||
For controls, a cost item may be defined as a control to a task.
|
||||
|
||||
For resources, any construction resource may be assigned to a task.
|
||||
|
||||
.. warning::
|
||||
|
||||
This function creates an **Input** relationship
|
||||
(``IfcRelAssignsToProcess``), meaning the product is *consumed* or
|
||||
*operated on* by the task — the typical case is demolition or
|
||||
maintenance.
|
||||
|
||||
If the task *constructs or installs* a product (e.g. erecting a wall
|
||||
or fitting a window), use :func:`assign_product` instead, which
|
||||
creates an **Output** relationship (``IfcRelAssignsToProduct``).
|
||||
|
||||
:param relating_process: The IfcProcess (typically IfcTask) that the
|
||||
input, control, or resource is related to.
|
||||
:param related_object: The IfcProduct (for input), IfcCostItem (for
|
||||
control) or IfcConstructionResource (for resource).
|
||||
:return: The newly created IfcRelAssignsToProcess relationship
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# Let's imagine we are creating a construction schedule. All tasks
|
||||
# need to be part of a work schedule.
|
||||
schedule = ifcopenshell.api.sequence.add_work_schedule(model, name="Construction Schedule A")
|
||||
|
||||
# Let's create a demolition task. Note that the predefined type is
|
||||
# important to distinguish types of tasks.
|
||||
task = ifcopenshell.api.sequence.add_task(model,
|
||||
work_schedule=schedule, name="Demolish existing", identification="A", predefined_type="DEMOLITION")
|
||||
|
||||
# Let's say we have a wall somewhere.
|
||||
wall = ifcopenshell.api.root.create_entity(model, ifc_class="IfcWall")
|
||||
|
||||
# The wall is an INPUT to the demolition task (it will be consumed).
|
||||
ifcopenshell.api.sequence.assign_process(model, relating_process=task, related_object=wall)
|
||||
|
||||
# For a construction task that BUILDS a wall, use assign_product instead:
|
||||
# build_task = ifcopenshell.api.sequence.add_task(model, ..., predefined_type="CONSTRUCTION")
|
||||
# ifcopenshell.api.sequence.assign_product(model, relating_product=wall, related_object=build_task)
|
||||
"""
|
||||
if related_object.HasAssignments:
|
||||
for assignment in related_object.HasAssignments:
|
||||
if assignment.is_a("IfcRelAssignsToProcess") and assignment.RelatingProcess == relating_process:
|
||||
return
|
||||
|
||||
operates_on = None
|
||||
if relating_process.OperatesOn:
|
||||
operates_on = relating_process.OperatesOn[0]
|
||||
|
||||
if operates_on:
|
||||
related_objects = list(operates_on.RelatedObjects)
|
||||
related_objects.append(related_object)
|
||||
operates_on.RelatedObjects = related_objects
|
||||
ifcopenshell.api.owner.update_owner_history(file, element=operates_on)
|
||||
else:
|
||||
operates_on = file.create_entity(
|
||||
"IfcRelAssignsToProcess",
|
||||
**{
|
||||
"GlobalId": ifcopenshell.guid.new(),
|
||||
"OwnerHistory": ifcopenshell.api.owner.create_owner_history(file),
|
||||
"RelatedObjects": [related_object],
|
||||
"RelatingProcess": relating_process,
|
||||
}
|
||||
)
|
||||
return operates_on
|
||||
@@ -0,0 +1,91 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.owner
|
||||
import ifcopenshell.guid
|
||||
|
||||
|
||||
def assign_product(
|
||||
file: ifcopenshell.file,
|
||||
relating_product: ifcopenshell.entity_instance,
|
||||
related_object: ifcopenshell.entity_instance,
|
||||
) -> ifcopenshell.entity_instance:
|
||||
"""Assigns a product to be produced as a result of a process
|
||||
|
||||
A construction task may result in products (e.g. a wall) being
|
||||
constructed. These task "Outputs" are defined in IFC through product
|
||||
relationships.
|
||||
|
||||
Not all tasks have Outputs. For example, maintenance tasks will
|
||||
typically not have any outputs.
|
||||
|
||||
See ifcopenshell.api.sequence.assign_process for Inputs and other types
|
||||
of process relationships that can be described in manufacturing
|
||||
process modeling.
|
||||
|
||||
:param relating_product: The IfcProduct that was constructed as a result
|
||||
of the task.
|
||||
:param related_object: The IfcProcess (typically IfcTask) of the
|
||||
construction task.
|
||||
:return: The newly created IfcRelAssignsToProduct relationship
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# Let's imagine we are creating a construction schedule. All tasks
|
||||
# need to be part of a work schedule.
|
||||
schedule = ifcopenshell.api.sequence.add_work_schedule(model, name="Construction Schedule A")
|
||||
|
||||
# Let's create a construction task. Note that the predefined type is
|
||||
# important to distinguish types of tasks.
|
||||
task = ifcopenshell.api.sequence.add_task(model,
|
||||
work_schedule=schedule, name="Build wall", identification="A", predefined_type="CONSTRUCTION")
|
||||
|
||||
# Let's say we have a wall somewhere.
|
||||
wall = ifcopenshell.api.root.create_entity(model, ifc_class="IfcWall")
|
||||
|
||||
# Let's construct that wall!
|
||||
ifcopenshell.api.sequence.assign_product(model, relating_product=wall, related_object=task)
|
||||
"""
|
||||
if related_object.HasAssignments:
|
||||
for assignment in related_object.HasAssignments:
|
||||
if assignment.is_a("IfcRelAssignsToProduct") and assignment.RelatingProduct == relating_product:
|
||||
return assignment
|
||||
|
||||
referenced_by = None
|
||||
if relating_product.ReferencedBy:
|
||||
referenced_by = relating_product.ReferencedBy[0]
|
||||
|
||||
if referenced_by:
|
||||
related_objects = list(referenced_by.RelatedObjects)
|
||||
related_objects.append(related_object)
|
||||
referenced_by.RelatedObjects = related_objects
|
||||
ifcopenshell.api.owner.update_owner_history(file, element=referenced_by)
|
||||
else:
|
||||
referenced_by = file.create_entity(
|
||||
"IfcRelAssignsToProduct",
|
||||
**{
|
||||
"GlobalId": ifcopenshell.guid.new(),
|
||||
"OwnerHistory": ifcopenshell.api.owner.create_owner_history(file),
|
||||
"RelatedObjects": [related_object],
|
||||
"RelatingProduct": relating_product,
|
||||
}
|
||||
)
|
||||
return referenced_by
|
||||
@@ -0,0 +1,118 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.sequence
|
||||
|
||||
|
||||
def assign_recurrence_pattern(
|
||||
file: ifcopenshell.file,
|
||||
parent: ifcopenshell.entity_instance,
|
||||
recurrence_type: ifcopenshell.util.sequence.RECURRENCE_TYPE = "WEEKLY",
|
||||
) -> ifcopenshell.entity_instance:
|
||||
"""Define a time to recur at a particular interval
|
||||
|
||||
There are two scenarios where you might want to define a recurring time
|
||||
pattern.
|
||||
|
||||
You might want a task to be scheduled at a recurring interval,
|
||||
this is common for maintenance tasks which need to be performed monthly,
|
||||
every 6 months, every year, etc.
|
||||
|
||||
Alternatively, you might be defining a work calendar, which defines
|
||||
working days or holidays. The working days might be every week from
|
||||
monday to friday ("every" week means it recurs every week), or the
|
||||
holidays might be the same every year.
|
||||
|
||||
The types of recurrence are:
|
||||
|
||||
- DAILY: every Nth (interval) day for up to X (Occurrences) occurrences.
|
||||
e.g. Every day, every 2 days, every day up to 5 times, etc
|
||||
- WEEKLY: every Nth (interval) MTWTFSS (WeekdayComponent) for up to X
|
||||
(Occurrences) occurrences. e.g. Every Monday, every weekday, every
|
||||
other saturday, etc
|
||||
- MONTHLY_BY_DAY_OF_MONTH: every Nth (DayComponent) of every Xth
|
||||
(Interval) Month up to Y (Occurrences) occurrences. e.g. Every 15th of
|
||||
the Month.
|
||||
- MONTHLY_BY_POSITION: Every Nth (Position) MTWTFSS (WeekdayComponent)
|
||||
of every Xth (Interval) Month up to Y (Occurrences) occurrences. e.g.
|
||||
Every second Tuesday of the Month.
|
||||
- YEARLY_BY_DAY_OF_MONTH: every Nth (DayComponent) of every JFMAMJJASOND
|
||||
(MonthComponent) month of every Yth (Interval) Year up to Z
|
||||
(Occurrences) occurrences. e.g. every 25th of December.
|
||||
- YEARLY_BY_POSITION: every Nth (Position) MTWTFSS (WeekdayComponent) of
|
||||
every JFMAMJJASOND (MonthComponent) month of every Yth (Interval)
|
||||
Year up to Z (Occurrences) occurrences. e.g. every third Wednesday
|
||||
of January.
|
||||
|
||||
These recurrence patterns are fairly standard in all calendar and
|
||||
scheduling applications.
|
||||
|
||||
:param parent: Either an IfcTaskTimeRecurring if you are defining a
|
||||
recurring schedule for a task, or IfcWorkTime if you are defining a
|
||||
recurring pattern for a workdays or holidays in a calendar.
|
||||
:param recurrence_type: One of the types of recurrences.
|
||||
:return: The newly created IfcRecurrencePattern
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# Let's create a new calendar.
|
||||
calendar = ifcopenshell.api.sequence.add_work_calendar(model)
|
||||
|
||||
# Let's start defining the times that we work during the week.
|
||||
work_time = ifcopenshell.api.sequence.add_work_time(model,
|
||||
work_calendar=calendar, time_type="WorkingTimes")
|
||||
|
||||
# We create a weekly recurrence
|
||||
pattern = ifcopenshell.api.sequence.assign_recurrence_pattern(model,
|
||||
parent=work_time, recurrence_type="WEEKLY")
|
||||
|
||||
# State that we work from weekdays 1 to 5 (i.e. Monday to Friday)
|
||||
ifcopenshell.api.sequence.edit_recurrence_pattern(model,
|
||||
recurrence_pattern=pattern, attributes={"WeekdayComponent": [1, 2, 3, 4, 5]})
|
||||
|
||||
# Let's imagine we are creating a maintenance schedule.
|
||||
schedule = ifcopenshell.api.sequence.add_work_schedule(model, name="Equipment Maintenance")
|
||||
|
||||
# Now let's imagine we have a task to maintain the chillers
|
||||
task = ifcopenshell.api.sequence.add_task(model,
|
||||
work_schedule=schedule, name="Chiller maintenance")
|
||||
|
||||
# Because it is a maintenance task, we must schedule a recurring time
|
||||
time = ifcopenshell.api.sequence.add_task_time(model, task=task, is_recurring=True)
|
||||
|
||||
# We create a monthly recurrence
|
||||
pattern = ifcopenshell.api.sequence.assign_recurrence_pattern(model,
|
||||
parent=work_time, recurrence_type="MONTHLY_BY_DAY_OF_MONTH")
|
||||
|
||||
# Specifically, the maintenance task must occur every 6 months
|
||||
ifcopenshell.api.sequence.edit_recurrence_pattern(model,
|
||||
recurrence_pattern=pattern, attributes={"DayComponent": [1], "Interval": 6})
|
||||
"""
|
||||
recurrence = file.create_entity("IfcRecurrencePattern", recurrence_type)
|
||||
|
||||
if parent.is_a("IfcWorkTime"):
|
||||
if (old_recurrence := parent.RecurrencePattern) and file.get_total_inverses(old_recurrence) == 1:
|
||||
file.remove(old_recurrence)
|
||||
parent.RecurrencePattern = recurrence
|
||||
elif parent.is_a("IfcTaskTimeRecurring"):
|
||||
if (recurrence_old := parent.Recurrence) and file.get_total_inverses(recurrence_old) == 1:
|
||||
file.remove(recurrence_old)
|
||||
parent.Recurrence = recurrence
|
||||
return recurrence
|
||||
@@ -0,0 +1,123 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.owner
|
||||
import ifcopenshell.api.sequence
|
||||
import ifcopenshell.guid
|
||||
|
||||
|
||||
def assign_sequence(
|
||||
file: ifcopenshell.file,
|
||||
relating_process: ifcopenshell.entity_instance,
|
||||
related_process: ifcopenshell.entity_instance,
|
||||
sequence_type: str = "FINISH_START",
|
||||
) -> ifcopenshell.entity_instance:
|
||||
"""Assign a sequential relationship between tasks
|
||||
|
||||
Tasks in construction sequencing typically have sequence relationships
|
||||
between them, indicating that one task must happen after another. This
|
||||
is used to automatically compute new start and end dates and cascade
|
||||
changes when dates are changed. This is also used to calculate critical
|
||||
paths and floats.
|
||||
|
||||
There are four types of sequence relationships, known as finish to
|
||||
start, finish to finish, start to start, and start to finish, sometimes
|
||||
abbreviated as a (FS, FF, SS, and SF). The most common is the finish to
|
||||
start relationship, indicating that the previous task must finish before
|
||||
the next task can start.
|
||||
|
||||
You must not create cyclical task sequences. This makes the computer
|
||||
unhappy.
|
||||
|
||||
Note that "previous" or "next" does not necessarily mean the task
|
||||
chronologically happens before or after. They simply indicate the order
|
||||
of the sequence relationship. For this reason, they are often called
|
||||
predecessor and successor tasks in the planning profession.
|
||||
|
||||
:param relating_process: The previous / predecessor task.
|
||||
:param related_process: The next / successor task.
|
||||
:param sequence_type: Choose from FINISH_START, FINISH_FINISH,
|
||||
START_START, or START_FINISH.
|
||||
:return: The newly created IfcRelSequence
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# Let's imagine we are creating a construction schedule. All tasks
|
||||
# need to be part of a work schedule.
|
||||
schedule = ifcopenshell.api.sequence.add_work_schedule(model, name="Construction Schedule A")
|
||||
|
||||
# Let's imagine a root construction task
|
||||
construction = ifcopenshell.api.sequence.add_task(model,
|
||||
work_schedule=schedule, name="Construction", identification="C")
|
||||
|
||||
# Let's imagine we're doing a typically formwork, reinforcement,
|
||||
# pour sequence. Let's start with the formwork. It'll take us 2
|
||||
# days.
|
||||
formwork = ifcopenshell.api.sequence.add_task(model,
|
||||
parent_task=construction, name="Formwork", identification="C.1")
|
||||
time = ifcopenshell.api.sequence.add_task_time(model, task=formwork)
|
||||
ifcopenshell.api.sequence.edit_task_time(model,
|
||||
task_time=time, attributes={"ScheduleStart": "2000-01-01", "ScheduleDuration": "P2D"})
|
||||
|
||||
# Now let's do the reinforcement. It'll take us another 2 days.
|
||||
reinforcement = ifcopenshell.api.sequence.add_task(model,
|
||||
parent_task=construction, name="Reinforcement", identification="C.2")
|
||||
time = ifcopenshell.api.sequence.add_task_time(model, task=reinforcement)
|
||||
ifcopenshell.api.sequence.edit_task_time(model,
|
||||
task_time=time, attributes={"ScheduleStart": "2000-01-01", "ScheduleDuration": "P2D"})
|
||||
|
||||
# Now the pour it It'll only take 1 day.
|
||||
pour = ifcopenshell.api.sequence.add_task(model,
|
||||
parent_task=construction, name="Reinforcement", identification="C.3")
|
||||
time = ifcopenshell.api.sequence.add_task_time(model, task=pour)
|
||||
ifcopenshell.api.sequence.edit_task_time(model,
|
||||
task_time=time, attributes={"ScheduleStart": "2000-01-01", "ScheduleDuration": "P1D"})
|
||||
|
||||
# Now let's say the formwork must finish before the reinforcement
|
||||
# can start, and the reinforcement must finish before the pour can
|
||||
# start. This is a typical finish to start relationship (FS).
|
||||
ifcopenshell.api.sequence.assign_sequence(model,
|
||||
relating_process=formwork, related_process=reinforcement)
|
||||
ifcopenshell.api.sequence.assign_sequence(model,
|
||||
relating_process=reinforcement, related_process=pour)
|
||||
|
||||
# Notice how we set all the scheduled start dates arbitrarily at
|
||||
# 2000-01-01. This is because we can ask IfcOpenShell to
|
||||
# automatically cascade the dates, starting from any task. This will
|
||||
# update the reinforcement date to be 2000-01-03 and the pour date
|
||||
# to be 2000-01-05.
|
||||
ifcopenshell.api.sequence.cascade_schedule(model, task=formwork)
|
||||
"""
|
||||
for rel in related_process.IsSuccessorFrom or []:
|
||||
if rel.RelatingProcess == relating_process:
|
||||
return rel
|
||||
rel = file.create_entity(
|
||||
"IfcRelSequence",
|
||||
**{
|
||||
"GlobalId": ifcopenshell.guid.new(),
|
||||
"OwnerHistory": ifcopenshell.api.owner.create_owner_history(file),
|
||||
"RelatingProcess": relating_process,
|
||||
"RelatedProcess": related_process,
|
||||
"SequenceType": sequence_type,
|
||||
}
|
||||
)
|
||||
ifcopenshell.api.sequence.cascade_schedule(file, task=relating_process)
|
||||
return rel
|
||||
@@ -0,0 +1,64 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from typing import Union
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.aggregate
|
||||
import ifcopenshell.api.project
|
||||
|
||||
|
||||
def assign_work_plan(
|
||||
file: ifcopenshell.file, work_schedule: ifcopenshell.entity_instance, work_plan: ifcopenshell.entity_instance
|
||||
) -> Union[ifcopenshell.entity_instance, None]:
|
||||
"""Assigns a work schedule to a work plan
|
||||
|
||||
Typically, work schedules would be assigned to a work plan at creation.
|
||||
However you may also delay this and do it manually afterwards.
|
||||
|
||||
:param work_schedule: The IfcWorkSchedule that will be assigned to the
|
||||
work plan.
|
||||
:param work_plan: The IfcWorkPlan for the schedule to be assigned to.
|
||||
:return: The IfcRelAggregates relationship
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# This will hold all our construction schedules
|
||||
work_plan = ifcopenshell.api.sequence.add_work_plan(model, name="Construction")
|
||||
|
||||
# Alternatively, if you create a schedule without a work plan ...
|
||||
schedule = ifcopenshell.api.sequence.add_work_schedule(model, name="Construction Schedule A")
|
||||
|
||||
# ... you can assign the work plan afterwards.
|
||||
ifcopenshell.api.sequence.assign_work_plan(work_schedule=schedule, work_plan=work_plan)
|
||||
"""
|
||||
# TODO: this is an ambiguity by buildingSMART
|
||||
# See https://forums.buildingsmart.org/t/is-the-ifcworkschedule-project-declaration-mutually-exclusive-to-aggregation-within-a-relating-ifcworkplan/3510
|
||||
ifcopenshell.api.project.unassign_declaration(
|
||||
file,
|
||||
definitions=[work_schedule],
|
||||
relating_context=file.by_type("IfcContext")[0],
|
||||
)
|
||||
rel_aggregates = ifcopenshell.api.aggregate.assign_object(
|
||||
file,
|
||||
products=[work_schedule],
|
||||
relating_object=work_plan,
|
||||
)
|
||||
return rel_aggregates
|
||||
@@ -0,0 +1,148 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import math
|
||||
from typing import Union
|
||||
|
||||
import ifcopenshell.api.sequence
|
||||
import ifcopenshell.util.date
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
def calculate_task_duration(file: ifcopenshell.file, task: ifcopenshell.entity_instance) -> None:
|
||||
"""Calculates the task duration based on resource usage
|
||||
|
||||
If a task has labour or equipment resources assigned to it, its duration
|
||||
may be parametrically derived from the scheduled work of the resource.
|
||||
For example, a labour resource with scheduled work of 10 working days
|
||||
and a resource utilisation of 200% (i.e. two labour teams) will imply
|
||||
that the task duration is 5 working days.
|
||||
|
||||
If this data is not available, such as if the task has no resources,
|
||||
then nothing happens.
|
||||
|
||||
:param task: The IfcTask to calculate the duration for.
|
||||
:return: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# Add our own crew
|
||||
crew = ifcopenshell.api.resource.add_resource(model, ifc_class="IfcCrewResource")
|
||||
|
||||
# Add some labour to our crew.
|
||||
labour = ifcopenshell.api.resource.add_resource(model,
|
||||
parent_resource=crew, ifc_class="IfcLaborResource")
|
||||
|
||||
# Labour resource is quantified in terms of time.
|
||||
quantity = ifcopenshell.api.resource.add_resource_quantity(model,
|
||||
resource=labour, ifc_class="IfcQuantityTime")
|
||||
|
||||
# Store the unit time used in hours
|
||||
ifcopenshell.api.resource.edit_resource_quantity(model,
|
||||
physical_quantity=quantity, attributes={"TimeValue": 8.0})
|
||||
|
||||
# Let's imagine we've used the resource for 10 days with a
|
||||
# utilisation of 200%.
|
||||
time = ifcopenshell.api.resource.add_resource_time(model, resource=labour)
|
||||
ifcopenshell.api.resource.edit_resource_time(model,
|
||||
resource_time=time, attributes={"ScheduleWork": "PT80H", "ScheduleUsage": 2})
|
||||
|
||||
# Let's imagine we are creating a construction schedule. All tasks
|
||||
# need to be part of a work schedule.
|
||||
schedule = ifcopenshell.api.sequence.add_work_schedule(model, name="Construction Schedule A")
|
||||
|
||||
# Let's create a construction task. Note that the predefined type is
|
||||
# important to distinguish types of tasks.
|
||||
task = ifcopenshell.api.sequence.add_task(model,
|
||||
work_schedule=schedule, name="Foundations", identification="A")
|
||||
|
||||
# Assign our resource to the task.
|
||||
ifcopenshell.api.sequence.assign_process(model, relating_process=task, related_object=labour)
|
||||
|
||||
# Now we can calculate the task duration based on the resource. This
|
||||
# will set task.TaskTime.ScheduleDuration to be P5D.
|
||||
ifcopenshell.api.sequence.calculate_task_duration(model, task=task)
|
||||
"""
|
||||
usecase = Usecase()
|
||||
usecase.file = file
|
||||
return usecase.execute(task)
|
||||
|
||||
|
||||
class Usecase:
|
||||
file: ifcopenshell.file
|
||||
|
||||
def execute(self, task: ifcopenshell.entity_instance) -> None:
|
||||
self.task = task
|
||||
self.seconds_per_workday = self.calculate_seconds_per_workday()
|
||||
duration = self.calculate_max_resource_usage_duration()
|
||||
if duration:
|
||||
self.set_task_duration(duration)
|
||||
|
||||
def calculate_seconds_per_workday(self) -> float:
|
||||
def get_work_schedule(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 get_work_schedule(rel.RelatingObject)
|
||||
|
||||
default_seconds_per_workday = 8 * 60 * 60
|
||||
work_schedule = get_work_schedule(self.task)
|
||||
if not work_schedule:
|
||||
return default_seconds_per_workday
|
||||
psets = ifcopenshell.util.element.get_psets(work_schedule)
|
||||
if (
|
||||
not psets
|
||||
or "Pset_WorkControlCommon" not in psets
|
||||
or "WorkDayDuration" not in psets["Pset_WorkControlCommon"]
|
||||
):
|
||||
return default_seconds_per_workday
|
||||
work_day_duration = ifcopenshell.util.date.ifc2datetime(psets["Pset_WorkControlCommon"]["WorkDayDuration"])
|
||||
return work_day_duration.seconds
|
||||
|
||||
def calculate_max_resource_usage_duration(self) -> float:
|
||||
max_duration = 0
|
||||
for rel in self.task.OperatesOn or []:
|
||||
for related_object in rel.RelatedObjects:
|
||||
if related_object.is_a("IfcConstructionResource"):
|
||||
duration = self.calculate_duration_in_days(related_object)
|
||||
if duration and duration > max_duration:
|
||||
max_duration = duration
|
||||
return max_duration
|
||||
|
||||
def calculate_duration_in_days(self, resource: ifcopenshell.entity_instance) -> Union[float, None]:
|
||||
def is_hourly_work(schedule_work):
|
||||
return "T" in schedule_work
|
||||
|
||||
if not resource.Usage or not resource.Usage.ScheduleWork:
|
||||
return
|
||||
schedule_usage = resource.Usage.ScheduleUsage or 1
|
||||
schedule_duration = ifcopenshell.util.date.ifc2datetime(resource.Usage.ScheduleWork)
|
||||
if is_hourly_work(resource.Usage.ScheduleWork):
|
||||
schedule_seconds = (schedule_duration.days * 24 * 60 * 60) + schedule_duration.seconds
|
||||
else:
|
||||
partial_days = schedule_duration.seconds / (24 * 60 * 60)
|
||||
schedule_seconds = (schedule_duration.days + partial_days) * self.seconds_per_workday
|
||||
return math.ceil((schedule_seconds / self.seconds_per_workday) / schedule_usage)
|
||||
|
||||
def set_task_duration(self, duration: float) -> None:
|
||||
if not (task_time := self.task.TaskTime):
|
||||
task_time = ifcopenshell.api.sequence.add_task_time(self.file, task=self.task)
|
||||
task_time.ScheduleDuration = f"P{duration}D"
|
||||
@@ -0,0 +1,345 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import datetime
|
||||
from typing import Optional, Union
|
||||
|
||||
import ifcopenshell.util.date
|
||||
import ifcopenshell.util.sequence
|
||||
from ifcopenshell.util.sequence import DURATION_TYPE
|
||||
|
||||
|
||||
def cascade_schedule(file: ifcopenshell.file, task: ifcopenshell.entity_instance) -> 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.
|
||||
:return: 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.sequence.add_task(model,
|
||||
work_schedule=work_schedule, name=name, predefined_type="CONSTRUCTION")
|
||||
|
||||
# Give it a time
|
||||
task_time = ifcopenshell.api.sequence.add_task_time(model, task=task)
|
||||
|
||||
# Arbitrarily set the task's scheduled time duration to be 1 week
|
||||
ifcopenshell.api.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.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.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.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.sequence.cascade_schedule(model, task=start_task)
|
||||
|
||||
# Calculate the critical path and floats.
|
||||
ifcopenshell.api.sequence.recalculate_schedule(model, work_schedule=schedule)
|
||||
"""
|
||||
usecase = Usecase()
|
||||
usecase.file = file
|
||||
return usecase.execute(task)
|
||||
|
||||
|
||||
class Usecase:
|
||||
file: ifcopenshell.file
|
||||
|
||||
def execute(self, task: ifcopenshell.entity_instance):
|
||||
self.calendar_cache = {}
|
||||
self.cascade_task(task, is_first_task=True)
|
||||
|
||||
def cascade_task(
|
||||
self,
|
||||
task: ifcopenshell.entity_instance,
|
||||
is_first_task: bool = False,
|
||||
task_sequence: Optional[list[ifcopenshell.entity_instance]] = None,
|
||||
) -> None:
|
||||
if task_sequence is None:
|
||||
task_sequence = []
|
||||
|
||||
if task in task_sequence:
|
||||
print("Warning! Recursive sequence is as follows:")
|
||||
for i, debug_task in enumerate(task_sequence):
|
||||
if i == 0:
|
||||
print("Starting at", debug_task)
|
||||
else:
|
||||
print("... is a predecessor to ...", debug_task)
|
||||
print("... which is cyclically a predecessor to ...", task)
|
||||
raise RecursionError("Recursive tasks found. Could not cascade schedule.")
|
||||
|
||||
if not task.TaskTime:
|
||||
return
|
||||
|
||||
duration = (
|
||||
ifcopenshell.util.date.ifc2datetime(task.TaskTime.ScheduleDuration)
|
||||
if task.TaskTime.ScheduleDuration
|
||||
else datetime.timedelta()
|
||||
)
|
||||
|
||||
finishes = []
|
||||
starts = []
|
||||
|
||||
for rel in ifcopenshell.util.sequence.get_sequence_assignment(task, "predecessor"):
|
||||
predecessor = rel.RelatingProcess
|
||||
predecessor_duration = (
|
||||
ifcopenshell.util.date.ifc2datetime(predecessor.TaskTime.ScheduleDuration)
|
||||
if predecessor.TaskTime and predecessor.TaskTime.ScheduleDuration
|
||||
else datetime.timedelta()
|
||||
)
|
||||
if rel.SequenceType == "FINISH_START":
|
||||
finish = self.get_task_time_attribute(predecessor, "ScheduleFinish")
|
||||
if not finish:
|
||||
continue
|
||||
days = 0 if predecessor_duration.days == 0 else 1
|
||||
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
|
||||
)
|
||||
duration_type = rel.TimeLag.DurationType
|
||||
if days:
|
||||
starts.append(
|
||||
datetime.datetime.combine(
|
||||
self.offset_date(finish, days, duration_type, self.get_calendar(task)),
|
||||
datetime.time(9),
|
||||
)
|
||||
)
|
||||
starts.append(
|
||||
datetime.datetime.combine(
|
||||
self.offset_date(
|
||||
finish,
|
||||
days,
|
||||
duration_type,
|
||||
self.get_calendar(predecessor),
|
||||
),
|
||||
datetime.time(9),
|
||||
)
|
||||
)
|
||||
else:
|
||||
starts.append(finish)
|
||||
elif rel.SequenceType == "START_START":
|
||||
start = self.get_task_time_attribute(predecessor, "ScheduleStart")
|
||||
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
|
||||
)
|
||||
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)))
|
||||
else:
|
||||
starts.append(start)
|
||||
elif rel.SequenceType == "FINISH_FINISH":
|
||||
finish = self.get_task_time_attribute(predecessor, "ScheduleFinish")
|
||||
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
|
||||
)
|
||||
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)))
|
||||
else:
|
||||
finishes.append(finish)
|
||||
elif rel.SequenceType == "START_FINISH":
|
||||
start = self.get_task_time_attribute(predecessor, "ScheduleStart")
|
||||
if not start:
|
||||
continue
|
||||
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
|
||||
)
|
||||
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)),
|
||||
datetime.time(17),
|
||||
)
|
||||
)
|
||||
finishes.append(
|
||||
datetime.datetime.combine(
|
||||
self.offset_date(
|
||||
start,
|
||||
days,
|
||||
duration_type,
|
||||
self.get_calendar(predecessor),
|
||||
),
|
||||
datetime.time(17),
|
||||
)
|
||||
)
|
||||
else:
|
||||
finishes.append(start)
|
||||
|
||||
if starts and finishes:
|
||||
start = max(starts)
|
||||
finish = max(finishes)
|
||||
potential_finish = ifcopenshell.util.sequence.get_start_or_finish_date(
|
||||
start,
|
||||
duration,
|
||||
task.TaskTime.DurationType,
|
||||
self.get_calendar(task),
|
||||
date_type="FINISH",
|
||||
)
|
||||
if potential_finish > finish:
|
||||
start_ifc = ifcopenshell.util.date.datetime2ifc(start, "IfcDateTime")
|
||||
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")
|
||||
else:
|
||||
finish_ifc = ifcopenshell.util.date.datetime2ifc(finish, "IfcDateTime")
|
||||
if task.TaskTime.ScheduleFinish == finish_ifc and not is_first_task:
|
||||
return
|
||||
task.TaskTime.ScheduleFinish = finish_ifc
|
||||
task.TaskTime.ScheduleStart = ifcopenshell.util.date.datetime2ifc(
|
||||
ifcopenshell.util.sequence.get_start_or_finish_date(
|
||||
finish,
|
||||
duration,
|
||||
task.TaskTime.DurationType,
|
||||
self.get_calendar(task),
|
||||
date_type="START",
|
||||
),
|
||||
"IfcDateTime",
|
||||
)
|
||||
elif finishes:
|
||||
finish = max(finishes)
|
||||
finish_ifc = ifcopenshell.util.date.datetime2ifc(finish, "IfcDateTime")
|
||||
if task.TaskTime.ScheduleFinish == finish_ifc and not is_first_task:
|
||||
return
|
||||
task.TaskTime.ScheduleFinish = finish_ifc
|
||||
task.TaskTime.ScheduleStart = ifcopenshell.util.date.datetime2ifc(
|
||||
ifcopenshell.util.sequence.get_start_or_finish_date(
|
||||
finish,
|
||||
duration,
|
||||
task.TaskTime.DurationType,
|
||||
self.get_calendar(task),
|
||||
date_type="START",
|
||||
),
|
||||
"IfcDateTime",
|
||||
)
|
||||
elif starts:
|
||||
start = max(starts)
|
||||
start_ifc = ifcopenshell.util.date.datetime2ifc(start, "IfcDateTime")
|
||||
if task.TaskTime.ScheduleStart == start_ifc and not is_first_task:
|
||||
return
|
||||
task.TaskTime.ScheduleStart = start_ifc
|
||||
task.TaskTime.ScheduleFinish = ifcopenshell.util.date.datetime2ifc(
|
||||
ifcopenshell.util.sequence.get_start_or_finish_date(
|
||||
start,
|
||||
duration,
|
||||
task.TaskTime.DurationType,
|
||||
self.get_calendar(task),
|
||||
date_type="FINISH",
|
||||
),
|
||||
"IfcDateTime",
|
||||
)
|
||||
|
||||
for rel in task.IsPredecessorTo:
|
||||
self.cascade_task(rel.RelatedProcess, task_sequence=task_sequence + [task])
|
||||
|
||||
for rel in task.IsNestedBy:
|
||||
[
|
||||
self.cascade_task(nested_task, task_sequence=task_sequence + [task])
|
||||
for nested_task in rel.RelatedObjects or []
|
||||
]
|
||||
|
||||
def get_lag_time_days(self, lag_time: ifcopenshell.entity_instance) -> int:
|
||||
return ifcopenshell.util.date.ifc2datetime(lag_time.LagValue.wrappedValue).days
|
||||
|
||||
def get_calendar(self, task: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
|
||||
if task.id() not in self.calendar_cache:
|
||||
self.calendar_cache[task.id()] = ifcopenshell.util.sequence.derive_calendar(task)
|
||||
return self.calendar_cache[task.id()]
|
||||
|
||||
def offset_date(
|
||||
self, date: datetime.datetime, days: int, duration_type: DURATION_TYPE, calendar: ifcopenshell.entity_instance
|
||||
) -> datetime.datetime:
|
||||
return ifcopenshell.util.sequence.offset_date(date, datetime.timedelta(days=days), duration_type, calendar)
|
||||
|
||||
def get_task_time_attribute(
|
||||
self, task: ifcopenshell.entity_instance, attribute: str
|
||||
) -> Union[datetime.datetime, None]:
|
||||
if task.TaskTime:
|
||||
value = getattr(task.TaskTime, attribute)
|
||||
if value:
|
||||
return ifcopenshell.util.date.ifc2datetime(value)
|
||||
@@ -0,0 +1,51 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell.api.control
|
||||
import ifcopenshell.api.sequence
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
def copy_work_schedule(
|
||||
file: ifcopenshell.file,
|
||||
work_schedule: ifcopenshell.entity_instance,
|
||||
) -> ifcopenshell.entity_instance:
|
||||
"""Copy a work schedule.
|
||||
|
||||
:param work_schedule: IfcWorkSchedule to copy.
|
||||
:return: The duplicated IfcWorkSchedule entity.
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
work_plan = ifcopenshell.api.sequence.add_work_plan(model, name="Construction")
|
||||
schedule = ifcopenshell.api.sequence.add_work_schedule(model,
|
||||
name="Construction Schedule A", work_plan=work_plan)
|
||||
new_schedule = ifcopenshell.api.sequence.copy_work_schedule(model, schedule)
|
||||
"""
|
||||
# Shared code logic with copy_cost_schedule.
|
||||
new_schedule = ifcopenshell.util.element.copy(file, work_schedule)
|
||||
|
||||
for rel in work_schedule.Controls:
|
||||
for task in rel.RelatedObjects:
|
||||
duplicated_tasks = ifcopenshell.api.sequence.duplicate_task(file, task)[1]
|
||||
# All other nested items are not connected to the work schedule explicitly.
|
||||
duplicated_task = duplicated_tasks[0]
|
||||
ifcopenshell.api.control.assign_control(file, new_schedule, [duplicated_task])
|
||||
return new_schedule
|
||||
@@ -0,0 +1,105 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021-2022 Dion Moult <dion@thinkmoult.com>, Yassine Oualid <yassine@sigmadimensions.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from typing import Optional, Union
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.control
|
||||
import ifcopenshell.api.owner
|
||||
import ifcopenshell.api.sequence
|
||||
import ifcopenshell.guid
|
||||
import ifcopenshell.util.sequence
|
||||
|
||||
|
||||
def create_baseline(
|
||||
file: ifcopenshell.file, work_schedule: ifcopenshell.entity_instance, name: Optional[str] = None
|
||||
) -> None:
|
||||
"""Creates a baseline for your Work Schedule
|
||||
|
||||
Using a IfcWorkSchdule having PredefinedType=PLANNED,
|
||||
We can create a baseline for our work schedule. This IfcWorkSchedule will have PredefinedType=BASELINE
|
||||
and the IfcWorkSchedule.CreationDate indicating the date of the baseline creation, and IfcWorkSchedule.Name indicating the name of the baseline.
|
||||
|
||||
The following relationships are also baselined:
|
||||
|
||||
* Same Tasks & attributes
|
||||
* Same Task Relationships
|
||||
* Same Construction Resources
|
||||
* Same Resource Relationships
|
||||
|
||||
:param work_schedule: The planned work_schedule to baseline
|
||||
:param name: baseline work schedule name
|
||||
:return: The baseline work_schedule
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# We have a Work Schedule
|
||||
planned_work_schedule = WorkSchedule(name="Design new feature",predefinedType="PLANNED", deadline="2023-03-01")
|
||||
|
||||
# And now we have a baseline for our Work Schedule
|
||||
baseline_work_schedule = ifcopenshell.api.sequence.create_baseline(file, work_schedule=planned_work_schedule, name="Baseline 1")
|
||||
"""
|
||||
usecase = Usecase()
|
||||
usecase.file = file
|
||||
return usecase.execute(work_schedule, name)
|
||||
|
||||
|
||||
class Usecase:
|
||||
file: ifcopenshell.file
|
||||
|
||||
def execute(self, work_schedule: ifcopenshell.entity_instance, name: Union[str, None]) -> None:
|
||||
# create work schedule
|
||||
if not work_schedule.PredefinedType == "PLANNED":
|
||||
return
|
||||
baseline_work_schedule = ifcopenshell.api.sequence.add_work_schedule(
|
||||
self.file, name=work_schedule.Name, predefined_type="BASELINE"
|
||||
)
|
||||
baseline_work_schedule.Name = name
|
||||
self.create_baseline_reference(work_schedule, baseline_work_schedule)
|
||||
for summary_task in ifcopenshell.util.sequence.get_root_tasks(work_schedule):
|
||||
res = ifcopenshell.api.sequence.duplicate_task(self.file, task=summary_task)
|
||||
assert isinstance(res, list)
|
||||
current, duplicate = res
|
||||
ifcopenshell.api.control.assign_control(
|
||||
self.file, relating_control=baseline_work_schedule, related_objects=[duplicate[0]]
|
||||
)
|
||||
for i, task in enumerate(current):
|
||||
self.create_baseline_reference(task, duplicate[i])
|
||||
|
||||
def create_baseline_reference(
|
||||
self, relating_object: ifcopenshell.entity_instance, related_object: ifcopenshell.entity_instance
|
||||
) -> ifcopenshell.entity_instance:
|
||||
referenced_by = None
|
||||
if relating_object.Declares:
|
||||
referenced_by = relating_object.Declares[0]
|
||||
if referenced_by:
|
||||
related_objects = list(referenced_by.RelatedObjects)
|
||||
related_objects.append(related_object)
|
||||
referenced_by.RelatedObjects = related_objects
|
||||
ifcopenshell.api.owner.update_owner_history(self.file, element=referenced_by)
|
||||
else:
|
||||
referenced_by = self.file.create_entity(
|
||||
"IfcRelDefinesByObject",
|
||||
GlobalId=ifcopenshell.guid.new(),
|
||||
OwnerHistory=ifcopenshell.api.owner.create_owner_history(self.file),
|
||||
RelatedObjects=[related_object],
|
||||
RelatingObject=relating_object,
|
||||
)
|
||||
return referenced_by
|
||||
@@ -0,0 +1,193 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021-2022 Dion Moult <dion@thinkmoult.com>, Yassine Oualid <yassine@sigmadimensions.com>
|
||||
#
|
||||
# This self.file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.nest
|
||||
import ifcopenshell.api.owner
|
||||
import ifcopenshell.api.sequence
|
||||
import ifcopenshell.guid
|
||||
import ifcopenshell.util.date
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
# TODO: inconsistent name with other copy_xxx api methods.
|
||||
def duplicate_task(
|
||||
file: ifcopenshell.file, task: ifcopenshell.entity_instance
|
||||
) -> tuple[list[ifcopenshell.entity_instance], list[ifcopenshell.entity_instance]]:
|
||||
"""Duplicates a task in the project
|
||||
|
||||
The following relationships are also duplicated:
|
||||
|
||||
* The copy will have the same attributes and property sets as the original task
|
||||
* The copy will be assigned to the parent task or work schedule
|
||||
* The copy will have duplicated nested tasks
|
||||
|
||||
:param task: The task to be duplicated
|
||||
:return: A tuple that consists of two lists of tasks:
|
||||
|
||||
- Original task and it's nested tasks.
|
||||
- Their corresponding duplicated tasks.
|
||||
|
||||
Example:
|
||||
.. code:: python
|
||||
|
||||
# We have a task
|
||||
original_task = ifcopenshell.api.sequence.add_task(
|
||||
model, work_schedule=work_schedule,
|
||||
name="Design new feature",
|
||||
)
|
||||
|
||||
# And now we have two
|
||||
original_tasks, duplicated_tasks = ifcopenshell.api.sequence.duplicate_task(original_task)
|
||||
print(duplicated_tasks[0]) # A copy of ``original_task``.
|
||||
"""
|
||||
usecase = Usecase()
|
||||
usecase.file = file
|
||||
return usecase.execute(task)
|
||||
|
||||
|
||||
class Usecase:
|
||||
file: ifcopenshell.file
|
||||
current: list[ifcopenshell.entity_instance]
|
||||
duplicate: list[ifcopenshell.entity_instance]
|
||||
|
||||
def execute(
|
||||
self, task: ifcopenshell.entity_instance
|
||||
) -> tuple[list[ifcopenshell.entity_instance], list[ifcopenshell.entity_instance]]:
|
||||
self.current = []
|
||||
self.duplicate = []
|
||||
self.duplicate_task(task)
|
||||
self.copy_sequence_relationship()
|
||||
return self.current, self.duplicate
|
||||
|
||||
def duplicate_task(self, task: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
|
||||
new_task = ifcopenshell.util.element.copy_deep(self.file, task)
|
||||
self.current.append(task)
|
||||
self.duplicate.append(new_task)
|
||||
self.copy_indirect_attributes(task, new_task)
|
||||
return new_task
|
||||
|
||||
def copy_indirect_attributes(
|
||||
self, from_element: ifcopenshell.entity_instance, to_element: ifcopenshell.entity_instance
|
||||
) -> None:
|
||||
for inverse in self.file.get_inverse(from_element):
|
||||
if inverse.is_a("IfcRelDefinesByProperties"):
|
||||
inverse = ifcopenshell.util.element.copy(self.file, inverse)
|
||||
inverse.RelatedObjects = [to_element]
|
||||
pset = ifcopenshell.util.element.copy_deep(self.file, inverse.RelatingPropertyDefinition)
|
||||
inverse.RelatingPropertyDefinition = pset
|
||||
elif inverse.is_a("IfcRelNests") and inverse.RelatingObject == from_element:
|
||||
nested_tasks = [e for e in inverse.RelatedObjects]
|
||||
if nested_tasks:
|
||||
new_tasks = []
|
||||
for t in nested_tasks:
|
||||
new_task = self.duplicate_task(t)
|
||||
new_tasks.append(new_task)
|
||||
inverse = ifcopenshell.util.element.copy(self.file, inverse)
|
||||
inverse.RelatingObject = to_element
|
||||
inverse.RelatedObjects = new_tasks
|
||||
ifcopenshell.api.nest.unassign_object(self.file, related_objects=new_tasks)
|
||||
ifcopenshell.api.nest.assign_object(
|
||||
self.file,
|
||||
related_objects=new_tasks,
|
||||
relating_object=to_element,
|
||||
)
|
||||
|
||||
elif inverse.is_a("IfcRelSequence") and (
|
||||
inverse.RelatingProcess == from_element or inverse.RelatedProcess == from_element
|
||||
):
|
||||
continue
|
||||
elif inverse.is_a("IfcRelAssignsToControl") and inverse.RelatingControl.is_a("IfcWorkSchedule"):
|
||||
continue
|
||||
elif inverse.is_a("IfcRelDefinesByObject"):
|
||||
continue
|
||||
else:
|
||||
for i, value in enumerate(inverse):
|
||||
if value == from_element:
|
||||
new_inverse = ifcopenshell.util.element.copy(self.file, inverse)
|
||||
new_inverse[i] = to_element
|
||||
elif isinstance(value, (tuple, list)) and from_element in value:
|
||||
new_value = list(value)
|
||||
new_value.append(to_element)
|
||||
inverse[i] = new_value
|
||||
|
||||
def copy_sequence_relationship(self) -> None:
|
||||
original_tasks = self.current
|
||||
duplicated_tasks = self.duplicate
|
||||
for i, original_task in enumerate(original_tasks):
|
||||
for inverse in self.file.get_inverse(original_task):
|
||||
if inverse.is_a("IfcRelSequence") and (
|
||||
inverse.RelatingProcess == original_task or inverse.RelatedProcess == original_task
|
||||
):
|
||||
original_task_index = original_tasks.index(original_task)
|
||||
duplicated_task = duplicated_tasks[original_task_index]
|
||||
relating_process, related_process = None, None
|
||||
if inverse.RelatingProcess == original_task:
|
||||
relating_process = duplicated_task
|
||||
else:
|
||||
related_process = duplicated_task
|
||||
if inverse.RelatedProcess in original_tasks:
|
||||
related_process_index = original_tasks.index(inverse.RelatedProcess)
|
||||
related_process = duplicated_tasks[related_process_index]
|
||||
else: # thus the related process is not part of the duplicated tasks
|
||||
related_process = inverse.RelatedProcess
|
||||
if inverse.RelatingProcess in original_tasks:
|
||||
relating_process_index = original_tasks.index(inverse.RelatingProcess)
|
||||
relating_process = duplicated_tasks[relating_process_index]
|
||||
else: # thus the relating process is not part of the duplicated tasks
|
||||
relating_process = inverse.RelatingProcess
|
||||
if relating_process and related_process:
|
||||
rel = ifcopenshell.api.sequence.assign_sequence(
|
||||
self.file,
|
||||
relating_process=relating_process,
|
||||
related_process=related_process,
|
||||
)
|
||||
if inverse.TimeLag:
|
||||
ifcopenshell.api.sequence.assign_lag_time(
|
||||
self.file,
|
||||
rel_sequence=rel,
|
||||
lag_value=(
|
||||
ifcopenshell.util.date.ifc2datetime(inverse.TimeLag.LagValue.wrappedValue)
|
||||
if inverse.TimeLag.LagValue
|
||||
else None
|
||||
),
|
||||
duration_type=inverse.TimeLag.DurationType,
|
||||
)
|
||||
|
||||
def create_object_reference(
|
||||
self, relating_object: ifcopenshell.entity_instance, related_object: ifcopenshell.entity_instance
|
||||
) -> ifcopenshell.entity_instance:
|
||||
referenced_by = None
|
||||
if relating_object.Declares:
|
||||
referenced_by = relating_object.Declares[0]
|
||||
if referenced_by:
|
||||
related_objects = list(referenced_by.RelatedObjects)
|
||||
related_objects.append(related_object)
|
||||
referenced_by.RelatedObjects = related_objects
|
||||
ifcopenshell.api.owner.update_owner_history(self.file, element=referenced_by)
|
||||
else:
|
||||
referenced_by = self.file.create_entity(
|
||||
"IfcRelDefinesByObject",
|
||||
**{
|
||||
"GlobalId": ifcopenshell.guid.new(),
|
||||
"OwnerHistory": ifcopenshell.api.owner.create_owner_history(self.file),
|
||||
"RelatedObjects": [related_object],
|
||||
"RelatingObject": relating_object,
|
||||
}
|
||||
)
|
||||
return referenced_by
|
||||
@@ -0,0 +1,84 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from typing import Any
|
||||
|
||||
import ifcopenshell.api.sequence
|
||||
import ifcopenshell.util.date
|
||||
|
||||
|
||||
def edit_lag_time(file: ifcopenshell.file, lag_time: ifcopenshell.entity_instance, attributes: dict[str, Any]) -> None:
|
||||
"""Edits the attributes of an IfcLagTime
|
||||
|
||||
For more information about the attributes and data types of an
|
||||
IfcLagTime, consult the IFC documentation.
|
||||
|
||||
:param lag_time: The IfcLagTime entity you want to edit
|
||||
:param attributes: a dictionary of attribute names and values.
|
||||
:return: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# Let's imagine we are creating a construction schedule. All tasks
|
||||
# need to be part of a work schedule.
|
||||
schedule = ifcopenshell.api.sequence.add_work_schedule(model, name="Construction Schedule A")
|
||||
|
||||
# Let's imagine a root construction task
|
||||
construction = ifcopenshell.api.sequence.add_task(model,
|
||||
work_schedule=schedule, name="Construction", identification="C")
|
||||
|
||||
# Let's imagine we're doing a typically formwork, reinforcement,
|
||||
# pour sequence. Let's start with the formwork. It'll take us 2
|
||||
# days.
|
||||
formwork = ifcopenshell.api.sequence.add_task(model,
|
||||
parent_task=construction, name="Formwork", identification="C.1")
|
||||
time = ifcopenshell.api.sequence.add_task_time(model, task=formwork)
|
||||
ifcopenshell.api.sequence.edit_task_time(model,
|
||||
task_time=time, attributes={"ScheduleStart": "2000-01-01", "ScheduleDuration": "P2D"})
|
||||
|
||||
# Now let's do the reinforcement. It'll take us another 2 days.
|
||||
reinforcement = ifcopenshell.api.sequence.add_task(model,
|
||||
parent_task=construction, name="Reinforcement", identification="C.2")
|
||||
time = ifcopenshell.api.sequence.add_task_time(model, task=reinforcement)
|
||||
ifcopenshell.api.sequence.edit_task_time(model,
|
||||
task_time=time, attributes={"ScheduleStart": "2000-01-01", "ScheduleDuration": "P2D"})
|
||||
|
||||
# Now let's say the formwork must finish before the reinforcement
|
||||
# can start. This is a typical finish to start relationship (FS).
|
||||
sequence = ifcopenshell.api.sequence.assign_sequence(model,
|
||||
relating_process=formwork, related_process=reinforcement)
|
||||
|
||||
# Now typically there would be no lag time between formwork and
|
||||
# reinforcement, but let's pretend that we had to allow 1 day gap
|
||||
# for whatever reason.
|
||||
lag = ifcopenshell.api.sequence.assign_lag_time(model, rel_sequence=sequence, lag_value="P1D")
|
||||
|
||||
# Or, let's make it 2 days instead.
|
||||
ifcopenshell.api.sequence.edit_lag_time(model, lag_time=lag, attributes={"LagValue": "P2D"})
|
||||
"""
|
||||
for name, value in attributes.items():
|
||||
if name == "LagValue" and value is not None:
|
||||
if isinstance(value, float):
|
||||
value = file.createIfcRatioMeasure(value)
|
||||
else:
|
||||
value = file.createIfcDuration(ifcopenshell.util.date.datetime2ifc(value, "IfcDuration"))
|
||||
setattr(lag_time, name, value)
|
||||
for rel in [r for r in file.get_inverse(lag_time) if r.is_a("IfcRelSequence")]:
|
||||
ifcopenshell.api.sequence.cascade_schedule(file, task=rel.RelatedProcess)
|
||||
@@ -0,0 +1,60 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from typing import Any
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.sequence
|
||||
|
||||
|
||||
def edit_recurrence_pattern(
|
||||
file: ifcopenshell.file, recurrence_pattern: ifcopenshell.entity_instance, attributes: dict[str, Any]
|
||||
) -> None:
|
||||
"""Edits the attributes of an IfcRecurrencePattern
|
||||
|
||||
For more information about the attributes and data types of an
|
||||
IfcRecurrencePattern, consult the IFC documentation.
|
||||
|
||||
:param recurrence_pattern: The IfcRecurrencePattern entity you want to edit
|
||||
:param attributes: a dictionary of attribute names and values.
|
||||
:return: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# Let's create a new calendar.
|
||||
calendar = ifcopenshell.api.sequence.add_work_calendar(model)
|
||||
|
||||
# Let's start defining the times that we work during the week.
|
||||
work_time = ifcopenshell.api.sequence.add_work_time(model,
|
||||
work_calendar=calendar, time_type="WorkingTimes")
|
||||
|
||||
# We create a weekly recurrence
|
||||
pattern = ifcopenshell.api.sequence.assign_recurrence_pattern(model,
|
||||
parent=work_time, recurrence_type="WEEKLY")
|
||||
|
||||
# State that we work from weekdays 1 to 5 (i.e. Monday to Friday)
|
||||
ifcopenshell.api.sequence.edit_recurrence_pattern(model,
|
||||
recurrence_pattern=pattern, attributes={"WeekdayComponent": [1, 2, 3, 4, 5]})
|
||||
"""
|
||||
for name, value in attributes.items():
|
||||
setattr(recurrence_pattern, name, value)
|
||||
|
||||
ifcopenshell.util.sequence.is_working_day.cache_clear()
|
||||
ifcopenshell.util.sequence.is_calendar_applicable.cache_clear()
|
||||
@@ -0,0 +1,66 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from typing import Any
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.sequence
|
||||
|
||||
|
||||
def edit_sequence(
|
||||
file: ifcopenshell.file, rel_sequence: ifcopenshell.entity_instance, attributes: dict[str, Any]
|
||||
) -> None:
|
||||
"""Edits the attributes of an IfcRelSequence
|
||||
|
||||
For more information about the attributes and data types of an
|
||||
IfcRelSequence, consult the IFC documentation.
|
||||
|
||||
:param rel_sequence: The IfcRelSequence entity you want to edit
|
||||
:param attributes: a dictionary of attribute names and values.
|
||||
:return: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# Let's imagine we are creating a construction schedule. All tasks
|
||||
# need to be part of a work schedule.
|
||||
schedule = ifcopenshell.api.sequence.add_work_schedule(model, name="Construction Schedule A")
|
||||
|
||||
# Let's imagine a root construction task
|
||||
construction = ifcopenshell.api.sequence.add_task(model,
|
||||
work_schedule=schedule, name="Construction", identification="C")
|
||||
|
||||
# Let's imagine we're building 2 zones, one after another.
|
||||
zone1 = ifcopenshell.api.sequence.add_task(model,
|
||||
parent_task=construction, name="Zone 1", identification="C.1")
|
||||
zone2 = ifcopenshell.api.sequence.add_task(model,
|
||||
parent_task=construction, name="Zone 2", identification="C.2")
|
||||
|
||||
# Zone 1 finishes, then zone 2 starts.
|
||||
sequence = ifcopenshell.api.sequence.assign_sequence(model,
|
||||
relating_process=zone1, related_process=zone2)
|
||||
|
||||
# What if they both started at the same time?
|
||||
ifcopenshell.api.sequence.edit_sequence(model,
|
||||
rel_sequence=sequence, attributes={"SequenceType": "START_START"})
|
||||
"""
|
||||
for name, value in attributes.items():
|
||||
setattr(rel_sequence, name, value)
|
||||
if "SequenceType" in attributes.keys():
|
||||
ifcopenshell.api.sequence.cascade_schedule(file, task=rel_sequence.RelatedProcess)
|
||||
@@ -0,0 +1,50 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
from typing import Any
|
||||
|
||||
import ifcopenshell
|
||||
|
||||
|
||||
def edit_task(file: ifcopenshell.file, task: ifcopenshell.entity_instance, attributes: dict[str, Any]) -> None:
|
||||
"""Edits the attributes of an IfcTask
|
||||
|
||||
For more information about the attributes and data types of an
|
||||
IfcTask, consult the IFC documentation.
|
||||
|
||||
:param task: The IfcTask entity you want to edit
|
||||
:param attributes: a dictionary of attribute names and values.
|
||||
:return: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# Let's imagine we are creating a construction schedule. All tasks
|
||||
# need to be part of a work schedule.
|
||||
schedule = ifcopenshell.api.sequence.add_work_schedule(model, name="Construction Schedule A")
|
||||
|
||||
# Add a root task to represent the design milestones, and major
|
||||
# project phases.
|
||||
task = ifcopenshell.api.sequence.add_task(model,
|
||||
work_schedule=schedule, name="Milestones", identification="A")
|
||||
|
||||
# Change the identification
|
||||
ifcopenshell.api.sequence.edit_task(model, task=task, attributes={"Identification": "M"})
|
||||
"""
|
||||
for name, value in attributes.items():
|
||||
setattr(task, name, value)
|
||||
@@ -0,0 +1,153 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import datetime
|
||||
from typing import Any
|
||||
|
||||
import ifcopenshell.api.resource
|
||||
import ifcopenshell.api.sequence
|
||||
import ifcopenshell.util.constraint
|
||||
import ifcopenshell.util.date
|
||||
import ifcopenshell.util.sequence
|
||||
|
||||
|
||||
def edit_task_time(
|
||||
file: ifcopenshell.file,
|
||||
task_time: ifcopenshell.entity_instance,
|
||||
attributes: dict[str, Any],
|
||||
) -> None:
|
||||
"""Edits the attributes of an IfcTaskTime
|
||||
|
||||
For more information about the attributes and data types of an
|
||||
IfcTaskTime, consult the IFC documentation.
|
||||
|
||||
:param task_time: The IfcTaskTime entity you want to edit
|
||||
:param attributes: a dictionary of attribute names and values.
|
||||
:return: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# Let's imagine we are creating a construction schedule. All tasks
|
||||
# need to be part of a work schedule.
|
||||
schedule = ifcopenshell.api.sequence.add_work_schedule(model, name="Construction Schedule A")
|
||||
|
||||
# Create a task to do formwork
|
||||
task = ifcopenshell.api.sequence.add_task(model,
|
||||
work_schedule=schedule, name="Formwork", identification="A")
|
||||
|
||||
# Let's say it takes 2 days and starts on the 1st of January, 2000
|
||||
time = ifcopenshell.api.sequence.add_task_time(model, task=formwork)
|
||||
ifcopenshell.api.sequence.edit_task_time(model,
|
||||
task_time=time, attributes={"ScheduleStart": "2000-01-01", "ScheduleDuration": "P2D"})
|
||||
"""
|
||||
usecase = Usecase()
|
||||
usecase.file = file
|
||||
return usecase.execute(task_time, attributes)
|
||||
|
||||
|
||||
class Usecase:
|
||||
file: ifcopenshell.file
|
||||
|
||||
def execute(self, task_time: ifcopenshell.entity_instance, attributes: dict[str, Any]) -> None:
|
||||
self.task_time = task_time
|
||||
self.task = self.get_task()
|
||||
self.calendar = ifcopenshell.util.sequence.derive_calendar(self.task)
|
||||
|
||||
# If the user specifies both an end date and a duration, the duration takes priority
|
||||
if attributes.get("ScheduleDuration", None) and "ScheduleFinish" in attributes.keys():
|
||||
del attributes["ScheduleFinish"]
|
||||
|
||||
duration_type = attributes.get("DurationType", self.task_time.DurationType)
|
||||
finish = attributes.get("ScheduleFinish", None)
|
||||
if finish:
|
||||
if isinstance(finish, str):
|
||||
finish = datetime.datetime.fromisoformat(finish)
|
||||
attributes["ScheduleFinish"] = datetime.datetime.combine(
|
||||
ifcopenshell.util.sequence.get_soonest_working_day(finish, duration_type, self.calendar),
|
||||
datetime.time(17),
|
||||
)
|
||||
start = attributes.get("ScheduleStart", None)
|
||||
if start:
|
||||
if isinstance(start, str):
|
||||
start = datetime.datetime.fromisoformat(start)
|
||||
attributes["ScheduleStart"] = datetime.datetime.combine(
|
||||
ifcopenshell.util.sequence.get_soonest_working_day(start, duration_type, self.calendar),
|
||||
datetime.time(9),
|
||||
)
|
||||
|
||||
for name, value in attributes.items():
|
||||
if value is not None:
|
||||
if "Start" in name or "Finish" in name or name == "StatusTime":
|
||||
value = ifcopenshell.util.date.datetime2ifc(value, "IfcDateTime")
|
||||
elif name == "ScheduleDuration" or name == "ActualDuration" or name == "RemainingTime":
|
||||
value = ifcopenshell.util.date.datetime2ifc(value, "IfcDuration")
|
||||
setattr(self.task_time, name, value)
|
||||
|
||||
if "ScheduleDuration" in attributes.keys() and task_time.ScheduleDuration and task_time.ScheduleStart:
|
||||
self.calculate_finish()
|
||||
elif attributes.get("ScheduleStart", None) and task_time.ScheduleDuration:
|
||||
self.calculate_finish()
|
||||
elif attributes.get("ScheduleFinish", None) and task_time.ScheduleStart:
|
||||
self.calculate_duration()
|
||||
|
||||
if task_time.ScheduleDuration and (
|
||||
"ScheduleStart" in attributes.keys()
|
||||
or "ScheduleFinish" in attributes.keys()
|
||||
or "ScheduleDuration" in attributes.keys()
|
||||
):
|
||||
ifcopenshell.api.sequence.cascade_schedule(self.file, task=self.task)
|
||||
if task_time.ScheduleDuration:
|
||||
self.handle_resource_calculation()
|
||||
|
||||
def calculate_finish(self):
|
||||
finish = ifcopenshell.util.sequence.get_start_or_finish_date(
|
||||
ifcopenshell.util.date.ifc2datetime(self.task_time.ScheduleStart),
|
||||
ifcopenshell.util.date.ifc2datetime(self.task_time.ScheduleDuration),
|
||||
self.task_time.DurationType,
|
||||
self.calendar,
|
||||
date_type="FINISH",
|
||||
)
|
||||
self.task_time.ScheduleFinish = ifcopenshell.util.date.datetime2ifc(finish, "IfcDateTime")
|
||||
|
||||
def calculate_duration(self):
|
||||
start = ifcopenshell.util.date.ifc2datetime(self.task_time.ScheduleStart)
|
||||
finish = ifcopenshell.util.date.ifc2datetime(self.task_time.ScheduleFinish)
|
||||
current_date = datetime.date(start.year, start.month, start.day)
|
||||
finish_date = datetime.date(finish.year, finish.month, finish.day)
|
||||
duration = datetime.timedelta(days=1)
|
||||
while current_date < finish_date:
|
||||
if self.task_time.DurationType == "ELAPSEDTIME" or not self.calendar:
|
||||
duration += datetime.timedelta(days=1)
|
||||
elif ifcopenshell.util.sequence.is_working_day(current_date, self.calendar):
|
||||
duration += datetime.timedelta(days=1)
|
||||
current_date += datetime.timedelta(days=1)
|
||||
self.task_time.ScheduleDuration = ifcopenshell.util.date.datetime2ifc(duration, "IfcDuration")
|
||||
|
||||
def get_task(self) -> ifcopenshell.entity_instance:
|
||||
return next(e for e in self.file.get_inverse(self.task_time) if e.is_a("IfcTask"))
|
||||
|
||||
def handle_resource_calculation(self):
|
||||
resources = ifcopenshell.util.sequence.get_task_resources(self.task, is_recursive=False)
|
||||
for resource in resources:
|
||||
if ifcopenshell.util.constraint.is_attribute_locked(resource, "Usage.ScheduleWork"):
|
||||
ifcopenshell.api.resource.calculate_resource_usage(self.file, resource=resource)
|
||||
# TODO: If the duration changes, this implies the productivity rate must change to accomModate the new Schedule Work to be calculated.
|
||||
# elif ifcopenshell.util.constraint.is_attribute_locked(resource, "Usage.ScheduleUsage"):
|
||||
# ifcopenshell.api.resource.calculate_resource_work(self.file, resource=resource)
|
||||
@@ -0,0 +1,47 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
from typing import Any
|
||||
|
||||
import ifcopenshell
|
||||
|
||||
|
||||
def edit_work_calendar(
|
||||
file: ifcopenshell.file, work_calendar: ifcopenshell.entity_instance, attributes: dict[str, Any]
|
||||
) -> None:
|
||||
"""Edits the attributes of an IfcWorkCalendar
|
||||
|
||||
For more information about the attributes and data types of an
|
||||
IfcWorkCalendar, consult the IFC documentation.
|
||||
|
||||
:param work_calendar: The IfcWorkCalendar entity you want to edit
|
||||
:param attributes: a dictionary of attribute names and values.
|
||||
:return: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# Let's create a new calendar.
|
||||
calendar = ifcopenshell.api.sequence.add_work_calendar(model, name="5 Day Week")
|
||||
|
||||
# Let's give it a description
|
||||
ifcopenshell.api.sequence.edit_work_calendar(model,
|
||||
work_calendar=calendar, attributes={"Description": "Monday to Friday 8 hour days"})
|
||||
"""
|
||||
for name, value in attributes.items():
|
||||
setattr(work_calendar, name, value)
|
||||
@@ -0,0 +1,53 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from typing import Any
|
||||
|
||||
import ifcopenshell.util.date
|
||||
|
||||
|
||||
def edit_work_plan(
|
||||
file: ifcopenshell.file, work_plan: ifcopenshell.entity_instance, attributes: dict[str, Any]
|
||||
) -> None:
|
||||
"""Edits the attributes of an IfcWorkPlan
|
||||
|
||||
For more information about the attributes and data types of an
|
||||
IfcWorkPlan, consult the IFC documentation.
|
||||
|
||||
:param work_plan: The IfcWorkPlan entity you want to edit
|
||||
:param attributes: a dictionary of attribute names and values.
|
||||
:return: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# This will hold all our construction schedules
|
||||
work_plan = ifcopenshell.api.sequence.add_work_plan(model, name="Construction")
|
||||
|
||||
# Let's give it a description
|
||||
ifcopenshell.api.sequence.edit_work_plan(model,
|
||||
work_plan=work_plan, attributes={"Description": "Construction of phase 1"})
|
||||
"""
|
||||
for name, value in attributes.items():
|
||||
if value:
|
||||
if "Date" in name or "Time" in name:
|
||||
value = ifcopenshell.util.date.datetime2ifc(value, "IfcDateTime")
|
||||
elif name == "Duration" or name == "TotalFloat":
|
||||
value = ifcopenshell.util.date.datetime2ifc(value, "IfcDuration")
|
||||
setattr(work_plan, name, value)
|
||||
@@ -0,0 +1,57 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from typing import Any
|
||||
|
||||
import ifcopenshell.util.date
|
||||
|
||||
|
||||
def edit_work_schedule(
|
||||
file: ifcopenshell.file, work_schedule: ifcopenshell.entity_instance, attributes: dict[str, Any]
|
||||
) -> None:
|
||||
"""Edits the attributes of an IfcWorkSchedule
|
||||
|
||||
For more information about the attributes and data types of an
|
||||
IfcWorkSchedule, consult the IFC documentation.
|
||||
|
||||
:param work_schedule: The IfcWorkSchedule entity you want to edit
|
||||
:param attributes: a dictionary of attribute names and values.
|
||||
:return: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# This will hold all our construction schedules
|
||||
work_plan = ifcopenshell.api.sequence.add_work_plan(model, name="Construction")
|
||||
|
||||
# Let's imagine this is one of our schedules in our work plan.
|
||||
schedule = ifcopenshell.api.sequence.add_work_schedule(model,
|
||||
name="Construction Schedule A", work_plan=work_plan)
|
||||
|
||||
# Let's give it a description
|
||||
ifcopenshell.api.sequence.edit_work_schedule(model,
|
||||
work_schedule=work_schedule, attributes={"Description": "3 crane design option"})
|
||||
"""
|
||||
for name, value in attributes.items():
|
||||
if value:
|
||||
if "Date" in name or "Time" in name:
|
||||
value = ifcopenshell.util.date.datetime2ifc(value, "IfcDateTime")
|
||||
elif name == "Duration" or name == "TotalFloat":
|
||||
value = ifcopenshell.util.date.datetime2ifc(value, "IfcDuration")
|
||||
setattr(work_schedule, name, value)
|
||||
@@ -0,0 +1,65 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from typing import Any
|
||||
|
||||
import ifcopenshell.util.date
|
||||
|
||||
|
||||
def edit_work_time(
|
||||
file: ifcopenshell.file,
|
||||
work_time: ifcopenshell.entity_instance,
|
||||
attributes: dict[str, Any],
|
||||
) -> None:
|
||||
"""Edits the attributes of an IfcWorkTime
|
||||
|
||||
For more information about the attributes and data types of an
|
||||
IfcWorkTime, consult the IFC documentation.
|
||||
|
||||
:param work_time: The IfcWorkTime entity you want to edit
|
||||
:param attributes: a dictionary of attribute names and values.
|
||||
:return: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# Let's create a new calendar.
|
||||
calendar = ifcopenshell.api.sequence.add_work_calendar(model)
|
||||
|
||||
# Let's start defining the times that we work during the week.
|
||||
work_time = ifcopenshell.api.sequence.add_work_time(model,
|
||||
work_calendar=calendar, time_type="WorkingTimes")
|
||||
|
||||
# If we don't specify any recurring time periods in our work time,
|
||||
# we need to specify a start and end date of the work time. It
|
||||
# starts at 0:00 on the start date and 24:00 at the end date.
|
||||
ifcopenshell.api.sequence.edit_work_time(model,
|
||||
work_time=work_time, attributes={"StartDate": "2000-01-01", "FinishDate": "2000-01-02"})
|
||||
"""
|
||||
for name, value in attributes.items():
|
||||
if name in ("Start", "StartDate"):
|
||||
value = ifcopenshell.util.date.datetime2ifc(value, "IfcDate")
|
||||
# 4 IfcWorktime Start
|
||||
work_time[4] = value
|
||||
elif name in ("Finish", "FinishDate"):
|
||||
value = ifcopenshell.util.date.datetime2ifc(value, "IfcDate")
|
||||
# 5 IfcWorktime Finish
|
||||
work_time[5] = value
|
||||
else:
|
||||
setattr(work_time, name, value)
|
||||
@@ -0,0 +1,521 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import datetime
|
||||
|
||||
import networkx as nx
|
||||
|
||||
import ifcopenshell.api.sequence
|
||||
import ifcopenshell.util.date
|
||||
import ifcopenshell.util.sequence
|
||||
|
||||
|
||||
def recalculate_schedule(file: ifcopenshell.file, work_schedule: ifcopenshell.entity_instance) -> 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.
|
||||
:return: 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
|
||||
return usecase.execute(work_schedule)
|
||||
|
||||
|
||||
class Usecase:
|
||||
file: ifcopenshell.file
|
||||
|
||||
def execute(self, work_schedule: ifcopenshell.entity_instance) -> None:
|
||||
self.work_schedule = work_schedule
|
||||
# The method implemented is the same as shown here:
|
||||
# https://www.youtube.com/watch?v=qTErIV6OqLg
|
||||
self.start_dates = []
|
||||
self.build_network_graph()
|
||||
|
||||
if not self.start_dates:
|
||||
return
|
||||
|
||||
is_cyclic = False
|
||||
attempts = 0
|
||||
self.pending_nodes = set(self.g.nodes)
|
||||
max_worst_case_attempts = pow(len(self.pending_nodes), 2)
|
||||
while self.pending_nodes:
|
||||
attempts += 1
|
||||
remaining_nodes = set()
|
||||
for pending_node in self.pending_nodes:
|
||||
if not self.forward_pass(pending_node):
|
||||
remaining_nodes.add(pending_node)
|
||||
self.pending_nodes = remaining_nodes
|
||||
|
||||
# As we parse nodes, the remaining attempts can drop dramatically, so we recalculate the upper limit
|
||||
max_remaining_attempts = pow(len(self.pending_nodes), 2)
|
||||
if max_remaining_attempts < max_worst_case_attempts:
|
||||
max_worst_case_attempts = max_remaining_attempts
|
||||
attempts = 0
|
||||
|
||||
if attempts > max_worst_case_attempts:
|
||||
is_cyclic = True
|
||||
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.")
|
||||
|
||||
self.pending_nodes = set(self.g.nodes)
|
||||
while self.pending_nodes:
|
||||
remaining_nodes = set()
|
||||
for pending_node in self.pending_nodes:
|
||||
if not self.backward_pass(pending_node):
|
||||
remaining_nodes.add(pending_node)
|
||||
self.pending_nodes = remaining_nodes
|
||||
|
||||
self.update_task_times()
|
||||
|
||||
def build_network_graph(self) -> None:
|
||||
self.sequence_type_map = {
|
||||
None: "FS",
|
||||
"START_START": "SS",
|
||||
"START_FINISH": "SF",
|
||||
"FINISH_START": "FS",
|
||||
"FINISH_FINISH": "FF",
|
||||
"USERDEFINED": "FS",
|
||||
"NOTDEFINED": "FS",
|
||||
}
|
||||
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)
|
||||
for rel in self.work_schedule.Controls:
|
||||
for related_object in rel.RelatedObjects:
|
||||
if not related_object.is_a("IfcTask"):
|
||||
continue
|
||||
self.add_node(related_object)
|
||||
self.g.add_edges_from(self.edges)
|
||||
|
||||
def add_node(self, task: ifcopenshell.entity_instance) -> None:
|
||||
if task.IsNestedBy:
|
||||
for rel in task.IsNestedBy:
|
||||
[self.add_node(o) for o in rel.RelatedObjects]
|
||||
return
|
||||
|
||||
if task.TaskTime and task.TaskTime.ScheduleDuration:
|
||||
duration = ifcopenshell.util.date.ifc2datetime(task.TaskTime.ScheduleDuration).days
|
||||
duration_type = task.TaskTime.DurationType
|
||||
else:
|
||||
duration = 0
|
||||
duration_type = "ELAPSEDTIME"
|
||||
|
||||
self.g.add_node(
|
||||
task.id(),
|
||||
duration=duration,
|
||||
duration_type=duration_type,
|
||||
calendar=ifcopenshell.util.sequence.derive_calendar(task),
|
||||
)
|
||||
|
||||
self.edges.extend(
|
||||
[
|
||||
(
|
||||
rel.RelatingProcess.id(),
|
||||
task.id(),
|
||||
{
|
||||
"lag_time": (
|
||||
0
|
||||
if not rel.TimeLag
|
||||
else ifcopenshell.util.date.ifc2datetime(rel.TimeLag.LagValue.wrappedValue).days
|
||||
),
|
||||
"type": self.sequence_type_map[rel.SequenceType],
|
||||
},
|
||||
)
|
||||
for rel in ifcopenshell.util.sequence.get_sequence_assignment(task, sequence="predecessor")
|
||||
]
|
||||
)
|
||||
|
||||
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
|
||||
if not successor_types:
|
||||
self.edges.append((task.id(), "finish", {"lag_time": 0, "type": "FF"}))
|
||||
|
||||
def update_task_times(self) -> None:
|
||||
for ifc_definition_id in self.g.nodes:
|
||||
if ifc_definition_id in ("start", "finish"):
|
||||
continue
|
||||
data = self.g.nodes[ifc_definition_id]
|
||||
task = self.file.by_id(ifc_definition_id)
|
||||
if not task.TaskTime:
|
||||
continue
|
||||
ifcopenshell.api.sequence.edit_task_time(
|
||||
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"),
|
||||
"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"),
|
||||
},
|
||||
)
|
||||
|
||||
def offset_date(self, date: datetime.datetime, days: int, node: dict) -> datetime.datetime:
|
||||
return ifcopenshell.util.sequence.offset_date(
|
||||
date, datetime.timedelta(days=days), node["duration_type"], node["calendar"]
|
||||
)
|
||||
|
||||
def forward_pass(self, node) -> bool:
|
||||
successors = self.g.successors(node)
|
||||
predecessors = list(self.g.predecessors(node))
|
||||
data = self.g.nodes[node]
|
||||
|
||||
if node == "start":
|
||||
data["early_start"] = min(self.start_dates)
|
||||
else:
|
||||
finishes = []
|
||||
starts = []
|
||||
if data.get("early_start") is not None:
|
||||
data["early_finish"] = ifcopenshell.util.sequence.get_start_or_finish_date(
|
||||
data["early_start"],
|
||||
datetime.timedelta(days=data["duration"]),
|
||||
data["duration_type"],
|
||||
data["calendar"],
|
||||
date_type="FINISH",
|
||||
)
|
||||
return True # we're done! We assume this task is constrained and finish processing it
|
||||
|
||||
for predecessor in predecessors:
|
||||
predecessor_data = self.g.nodes[predecessor]
|
||||
edge = self.g[predecessor][node]
|
||||
if edge["type"] == "FS":
|
||||
finish = predecessor_data.get("early_finish")
|
||||
if finish is None:
|
||||
return
|
||||
days = 0 if predecessor_data["duration"] == 0 else 1
|
||||
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, predecessor_data),
|
||||
datetime.time(9),
|
||||
)
|
||||
)
|
||||
else:
|
||||
starts.append(finish)
|
||||
elif edge["type"] == "SS":
|
||||
start = predecessor_data.get("early_start")
|
||||
if start is None:
|
||||
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))
|
||||
else:
|
||||
starts.append(start)
|
||||
elif edge["type"] == "FF":
|
||||
finish = predecessor_data.get("early_finish")
|
||||
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))
|
||||
else:
|
||||
finishes.append(finish)
|
||||
elif edge["type"] == "SF":
|
||||
start = predecessor_data.get("early_start")
|
||||
if start is None:
|
||||
return
|
||||
days = -1
|
||||
if edge["lag_time"]:
|
||||
days += edge["lag_time"]
|
||||
if days or edge["lag_time"]:
|
||||
finishes.append(
|
||||
datetime.datetime.combine(self.offset_date(start, days, data), datetime.time(17))
|
||||
)
|
||||
finishes.append(
|
||||
datetime.datetime.combine(
|
||||
self.offset_date(start, days, predecessor_data),
|
||||
datetime.time(17),
|
||||
)
|
||||
)
|
||||
else:
|
||||
finishes.append(start)
|
||||
if starts and finishes:
|
||||
data["early_start"] = max(starts)
|
||||
data["early_finish"] = max(finishes)
|
||||
potential_finish = ifcopenshell.util.sequence.get_start_or_finish_date(
|
||||
data["early_start"],
|
||||
datetime.timedelta(days=data["duration"]),
|
||||
data["duration_type"],
|
||||
data["calendar"],
|
||||
date_type="FINISH",
|
||||
)
|
||||
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_finish"],
|
||||
datetime.timedelta(days=data["duration"]),
|
||||
data["duration_type"],
|
||||
data["calendar"],
|
||||
date_type="START",
|
||||
)
|
||||
elif finishes:
|
||||
data["early_finish"] = max(finishes)
|
||||
elif starts:
|
||||
data["early_start"] = max(starts)
|
||||
else:
|
||||
print("How did this happen?")
|
||||
|
||||
if data.get("early_finish") is None:
|
||||
data["early_finish"] = ifcopenshell.util.sequence.get_start_or_finish_date(
|
||||
data["early_start"],
|
||||
datetime.timedelta(days=data["duration"]),
|
||||
data["duration_type"],
|
||||
data["calendar"],
|
||||
date_type="FINISH",
|
||||
)
|
||||
elif data.get("early_start") is None:
|
||||
data["early_start"] = ifcopenshell.util.sequence.get_start_or_finish_date(
|
||||
data["early_finish"],
|
||||
datetime.timedelta(days=data["duration"]),
|
||||
data["duration_type"],
|
||||
data["calendar"],
|
||||
date_type="START",
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
def backward_pass(self, node) -> bool:
|
||||
successors = list(self.g.successors(node))
|
||||
predecessors = self.g.predecessors(node)
|
||||
data = self.g.nodes[node]
|
||||
free_floats = []
|
||||
|
||||
if node == "finish":
|
||||
data["late_finish"] = data["early_finish"]
|
||||
else:
|
||||
finishes = []
|
||||
starts = []
|
||||
for successor in successors:
|
||||
successor_data = self.g.nodes[successor]
|
||||
edge = self.g[node][successor]
|
||||
if edge["type"] == "FS":
|
||||
start = successor_data.get("late_start")
|
||||
if start is None:
|
||||
return
|
||||
days = 1
|
||||
if edge["lag_time"]:
|
||||
days += edge["lag_time"]
|
||||
if days or edge["lag_time"]:
|
||||
finishes.append(
|
||||
datetime.datetime.combine(self.offset_date(start, -days, data), datetime.time(17))
|
||||
)
|
||||
finishes.append(
|
||||
datetime.datetime.combine(
|
||||
self.offset_date(start, -days, successor_data),
|
||||
datetime.time(17),
|
||||
)
|
||||
)
|
||||
else:
|
||||
finishes.append(start)
|
||||
free_floats.append(
|
||||
self.calculate_free_float(
|
||||
data["early_finish"].date() + datetime.timedelta(days=1),
|
||||
successor_data["early_start"].date(),
|
||||
edge["lag_time"],
|
||||
data,
|
||||
successor_data,
|
||||
)
|
||||
)
|
||||
elif edge["type"] == "SS":
|
||||
start = successor_data.get("late_start")
|
||||
if start is None:
|
||||
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))
|
||||
else:
|
||||
starts.append(start)
|
||||
free_floats.append(
|
||||
self.calculate_free_float(
|
||||
data["early_start"],
|
||||
successor_data["early_start"],
|
||||
edge["lag_time"],
|
||||
data,
|
||||
successor_data,
|
||||
)
|
||||
)
|
||||
elif edge["type"] == "FF":
|
||||
finish = successor_data.get("late_finish")
|
||||
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))
|
||||
else:
|
||||
finishes.append(finish)
|
||||
free_floats.append(
|
||||
self.calculate_free_float(
|
||||
data["early_finish"],
|
||||
successor_data["early_finish"],
|
||||
edge["lag_time"],
|
||||
data,
|
||||
successor_data,
|
||||
)
|
||||
)
|
||||
elif edge["type"] == "SF":
|
||||
finish = successor_data.get("late_finish")
|
||||
if finish is None:
|
||||
return
|
||||
days = 0 if successor_data["duration"] == 0 else -1
|
||||
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, successor_data),
|
||||
datetime.time(9),
|
||||
)
|
||||
)
|
||||
else:
|
||||
starts.append(finish)
|
||||
free_floats.append(
|
||||
self.calculate_free_float(
|
||||
data["early_start"],
|
||||
successor_data["early_finish"],
|
||||
edge["lag_time"],
|
||||
data,
|
||||
successor_data,
|
||||
)
|
||||
)
|
||||
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(
|
||||
data["late_start"],
|
||||
datetime.timedelta(days=data["duration"]),
|
||||
data["duration_type"],
|
||||
data["calendar"],
|
||||
date_type="FINISH",
|
||||
)
|
||||
else:
|
||||
data["late_start"] = ifcopenshell.util.sequence.get_start_or_finish_date(
|
||||
data["late_finish"],
|
||||
datetime.timedelta(days=data["duration"]),
|
||||
data["duration_type"],
|
||||
data["calendar"],
|
||||
date_type="START",
|
||||
)
|
||||
elif finishes:
|
||||
data["late_finish"] = min(finishes)
|
||||
elif starts:
|
||||
data["late_start"] = min(starts)
|
||||
else:
|
||||
print("How did this happen?")
|
||||
|
||||
if data.get("late_finish") is None:
|
||||
data["late_finish"] = ifcopenshell.util.sequence.get_start_or_finish_date(
|
||||
data["late_start"],
|
||||
datetime.timedelta(days=data["duration"]),
|
||||
data["duration_type"],
|
||||
data["calendar"],
|
||||
date_type="FINISH",
|
||||
)
|
||||
elif data.get("late_start") is None:
|
||||
data["late_start"] = ifcopenshell.util.sequence.get_start_or_finish_date(
|
||||
data["late_finish"],
|
||||
datetime.timedelta(days=data["duration"]),
|
||||
data["duration_type"],
|
||||
data["calendar"],
|
||||
date_type="START",
|
||||
)
|
||||
|
||||
if data["duration_type"] == "WORKTIME":
|
||||
data["total_float"] = datetime.timedelta(
|
||||
days=ifcopenshell.util.sequence.count_working_days(
|
||||
data["early_finish"], data["late_finish"], data["calendar"]
|
||||
)
|
||||
)
|
||||
else:
|
||||
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["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
|
||||
if data["free_float"] and data["free_float"].seconds == 60 * 60 * 8:
|
||||
data["free_float"] = datetime.timedelta(days=data["free_float"].days + 1)
|
||||
|
||||
return True
|
||||
|
||||
def calculate_free_float(
|
||||
self,
|
||||
predecessor_date: datetime.datetime,
|
||||
successor_date: datetime.datetime,
|
||||
lag_time: int,
|
||||
predecessor_data: dict,
|
||||
successor_data: dict,
|
||||
) -> datetime.timedelta:
|
||||
if not lag_time:
|
||||
min_successor_date = successor_date
|
||||
else:
|
||||
min_successor_date = min(
|
||||
(
|
||||
self.offset_date(successor_date, -lag_time, predecessor_data),
|
||||
self.offset_date(successor_date, -lag_time, successor_data),
|
||||
)
|
||||
)
|
||||
if predecessor_data["duration_type"] == "WORKTIME":
|
||||
return datetime.timedelta(
|
||||
days=ifcopenshell.util.sequence.count_working_days(
|
||||
predecessor_date, min_successor_date, predecessor_data["calendar"]
|
||||
)
|
||||
)
|
||||
return min_successor_date - predecessor_date
|
||||
@@ -0,0 +1,135 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.nest
|
||||
import ifcopenshell.api.project
|
||||
import ifcopenshell.api.pset
|
||||
import ifcopenshell.api.sequence
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
def remove_task(file: ifcopenshell.file, task: ifcopenshell.entity_instance) -> None:
|
||||
"""Removes a task
|
||||
|
||||
All subtasks are also removed recursively. Any relationships such as
|
||||
sequences or controls are also removed.
|
||||
|
||||
:param task: The IfcTask to remove.
|
||||
:return: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# Let's imagine we are creating a construction schedule. All tasks
|
||||
# need to be part of a work schedule.
|
||||
schedule = ifcopenshell.api.sequence.add_work_schedule(model, name="Construction Schedule A")
|
||||
|
||||
# Add a root task to represent the design milestones, and major
|
||||
# project phases.
|
||||
ifcopenshell.api.sequence.add_task(model,
|
||||
work_schedule=schedule, name="Milestones", identification="A")
|
||||
design = ifcopenshell.api.sequence.add_task(model,
|
||||
work_schedule=schedule, name="Design", identification="B")
|
||||
ifcopenshell.api.sequence.add_task(model,
|
||||
work_schedule=schedule, name="Construction", identification="C")
|
||||
|
||||
# Ah, let's delete the design section, who needs it anyway we'll
|
||||
# just fix it on site.
|
||||
ifcopenshell.api.sequence.remove_task(model, task=design)
|
||||
"""
|
||||
# TODO: do a deep purge
|
||||
ifcopenshell.api.project.unassign_declaration(
|
||||
file,
|
||||
definitions=[task],
|
||||
relating_context=file.by_type("IfcContext")[0],
|
||||
)
|
||||
if task_time := task.TaskTime:
|
||||
if task_time.is_a("IfcTaskTimeRecurring"):
|
||||
ifcopenshell.api.sequence.unassign_recurrence_pattern(file, task_time.Recurrence)
|
||||
file.remove(task_time)
|
||||
|
||||
# Handle IfcRelNests.
|
||||
if rels := task.IsNestedBy:
|
||||
subtasks = rels[0].RelatedObjects
|
||||
# Use batching for optimization.
|
||||
ifcopenshell.api.nest.unassign_object(file, subtasks)
|
||||
for task_ in subtasks:
|
||||
ifcopenshell.api.sequence.remove_task(file, task=task_)
|
||||
if task.Nests:
|
||||
ifcopenshell.api.nest.unassign_object(file, [task])
|
||||
|
||||
for inverse in file.get_inverse(task):
|
||||
if inverse.is_a("IfcRelSequence"):
|
||||
history = inverse.OwnerHistory
|
||||
file.remove(inverse)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(file, history)
|
||||
elif inverse.is_a("IfcRelAssignsToControl"):
|
||||
if inverse.RelatingControl == task or len(inverse.RelatedObjects) == 1:
|
||||
history = inverse.OwnerHistory
|
||||
file.remove(inverse)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(file, history)
|
||||
else:
|
||||
related_objects = list(inverse.RelatedObjects)
|
||||
related_objects.remove(task)
|
||||
inverse.RelatedObjects = related_objects
|
||||
elif inverse.is_a("IfcRelDefinesByProperties"):
|
||||
ifcopenshell.api.pset.remove_pset(
|
||||
file,
|
||||
product=task,
|
||||
pset=inverse.RelatingPropertyDefinition,
|
||||
)
|
||||
elif inverse.is_a("IfcRelAssignsToProcess"):
|
||||
if inverse.RelatingProcess == task or len(inverse.RelatedObjects) == 1:
|
||||
history = inverse.OwnerHistory
|
||||
file.remove(inverse)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(file, history)
|
||||
elif inverse.is_a("IfcRelAssignsToProduct"):
|
||||
if inverse.RelatingProduct == task or len(inverse.RelatedObjects) == 1:
|
||||
history = inverse.OwnerHistory
|
||||
file.remove(inverse)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(file, history)
|
||||
else:
|
||||
related_objects = list(inverse.RelatedObjects)
|
||||
related_objects.remove(task)
|
||||
inverse.RelatedObjects = related_objects
|
||||
elif inverse.is_a("IfcRelAssignsToObject"):
|
||||
if inverse.RelatingObject == task or len(inverse.RelatedObjects) == 1:
|
||||
history = inverse.OwnerHistory
|
||||
file.remove(inverse)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(file, history)
|
||||
else:
|
||||
related_objects = list(inverse.RelatedObjects)
|
||||
related_objects.remove(task)
|
||||
inverse.RelatedObjects = related_objects
|
||||
elif inverse.is_a("IfcRelAssignsToProcess"):
|
||||
history = inverse.OwnerHistory
|
||||
file.remove(inverse)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(file, history)
|
||||
|
||||
history = task.OwnerHistory
|
||||
file.remove(task)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(file, history)
|
||||
@@ -0,0 +1,56 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell.api
|
||||
|
||||
|
||||
def remove_time_period(file: ifcopenshell.file, time_period: ifcopenshell.entity_instance) -> None:
|
||||
"""Removes a time period
|
||||
|
||||
:param time_period: The IfcTimePeriod to remove.
|
||||
:return: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# Let's create a new calendar.
|
||||
calendar = ifcopenshell.api.sequence.add_work_calendar(model)
|
||||
|
||||
# Let's start defining the times that we work during the week.
|
||||
work_time = ifcopenshell.api.sequence.add_work_time(model,
|
||||
work_calendar=calendar, time_type="WorkingTimes")
|
||||
|
||||
# We create a weekly recurrence
|
||||
pattern = ifcopenshell.api.sequence.assign_recurrence_pattern(model,
|
||||
parent=work_time, recurrence_type="WEEKLY")
|
||||
|
||||
# State that we work from weekdays 1 to 5 (i.e. Monday to Friday)
|
||||
ifcopenshell.api.sequence.edit_recurrence_pattern(model,
|
||||
recurrence_pattern=pattern, attributes={"WeekdayComponent": [1, 2, 3, 4, 5]})
|
||||
|
||||
# The morning work session, lunch, then the afternoon work session.
|
||||
morning = ifcopenshell.api.sequence.add_time_period(model,
|
||||
recurrence_pattern=pattern, start_time="09:00", end_time="12:00")
|
||||
afternoon = ifcopenshell.api.sequence.add_time_period(model,
|
||||
recurrence_pattern=pattern, start_time="13:00", end_time="17:00")
|
||||
|
||||
# Let's take the afternoon off!
|
||||
ifcopenshell.api.sequence.remove_time_period(model, time_period=afternoon)
|
||||
"""
|
||||
file.remove(time_period)
|
||||
@@ -0,0 +1,71 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.control
|
||||
import ifcopenshell.api.project
|
||||
import ifcopenshell.api.sequence
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
def remove_work_calendar(file: ifcopenshell.file, work_calendar: ifcopenshell.entity_instance) -> None:
|
||||
"""Removes a work calendar
|
||||
|
||||
All relationships are also removed, such as if a task is set to use that
|
||||
calendar.
|
||||
|
||||
:param work_calendar: The IfcWorkCalendar to remove
|
||||
:return: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# Let's create a new calendar.
|
||||
calendar = ifcopenshell.api.sequence.add_work_calendar(model, name="5 Day Week")
|
||||
|
||||
# And remove it immediately
|
||||
ifcopenshell.api.sequence.remove_work_calendar(model, work_calendar=calendar)
|
||||
"""
|
||||
# TODO: do a deep purge
|
||||
ifcopenshell.api.project.unassign_declaration(
|
||||
file,
|
||||
definitions=[work_calendar],
|
||||
relating_context=file.by_type("IfcContext")[0],
|
||||
)
|
||||
if work_calendar.Controls:
|
||||
for rel in work_calendar.Controls:
|
||||
for related_object in rel.RelatedObjects:
|
||||
ifcopenshell.api.control.unassign_control(
|
||||
file,
|
||||
relating_control=work_calendar,
|
||||
related_objects=[related_object],
|
||||
)
|
||||
|
||||
# Currently in API work times are created already attached
|
||||
# to the work calendar, so they are never reused.
|
||||
for working_time in work_calendar.WorkingTimes or []:
|
||||
ifcopenshell.api.sequence.remove_work_time(file, work_time=working_time)
|
||||
|
||||
for exception_time in work_calendar.ExceptionTimes or []:
|
||||
ifcopenshell.api.sequence.remove_work_time(file, work_time=exception_time)
|
||||
|
||||
history = work_calendar.OwnerHistory
|
||||
file.remove(work_calendar)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(file, history)
|
||||
@@ -0,0 +1,57 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.aggregate
|
||||
import ifcopenshell.api.project
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
def remove_work_plan(file: ifcopenshell.file, work_plan: ifcopenshell.entity_instance) -> None:
|
||||
"""Removes a work plan
|
||||
|
||||
Note that schedules that are grouped under the work plan are not
|
||||
removed.
|
||||
|
||||
:param work_plan: The IfcWorkPlan to remove.
|
||||
:return: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# This will hold all our construction schedules
|
||||
work_plan = ifcopenshell.api.sequence.add_work_plan(model, name="Construction")
|
||||
|
||||
# And remove it immediately
|
||||
ifcopenshell.api.sequence.remove_work_plan(model, work_plan=work_plan)
|
||||
"""
|
||||
ifcopenshell.api.project.unassign_declaration(
|
||||
file,
|
||||
definitions=[work_plan],
|
||||
relating_context=file.by_type("IfcContext")[0],
|
||||
)
|
||||
|
||||
related_objects = [obj for rel in work_plan.IsDecomposedBy for obj in rel.RelatedObjects]
|
||||
if related_objects:
|
||||
ifcopenshell.api.aggregate.unassign_object(file, related_objects)
|
||||
|
||||
history = work_plan.OwnerHistory
|
||||
file.remove(work_plan)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(file, history)
|
||||
@@ -0,0 +1,83 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.aggregate
|
||||
import ifcopenshell.api.project
|
||||
import ifcopenshell.api.sequence
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
def remove_work_schedule(file: ifcopenshell.file, work_schedule: ifcopenshell.entity_instance) -> None:
|
||||
"""Removes a work schedule
|
||||
|
||||
All tasks in the work schedule are also removed recursively.
|
||||
|
||||
:param work_schedule: The IfcWorkSchedule to remove.
|
||||
:return: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# This will hold all our construction schedules
|
||||
work_plan = ifcopenshell.api.sequence.add_work_plan(model, name="Construction")
|
||||
|
||||
# Let's imagine this is one of our schedules in our work plan.
|
||||
schedule = ifcopenshell.api.sequence.add_work_schedule(model,
|
||||
name="Construction Schedule A", work_plan=work_plan)
|
||||
|
||||
# And remove it immediately
|
||||
ifcopenshell.api.sequence.remove_work_schedule(model, work_schedule=schedule)
|
||||
"""
|
||||
# TODO: do a deep purge
|
||||
ifcopenshell.api.project.unassign_declaration(
|
||||
file, definitions=[work_schedule], relating_context=file.by_type("IfcContext")[0]
|
||||
)
|
||||
|
||||
if work_schedule.Declares:
|
||||
for rel in work_schedule.Declares:
|
||||
for work_schedule_ in rel.RelatedObjects:
|
||||
ifcopenshell.api.sequence.remove_work_schedule(file, work_schedule=work_schedule_)
|
||||
|
||||
# Unassign from work plans.
|
||||
if work_schedule.Decomposes:
|
||||
ifcopenshell.api.aggregate.unassign_object(file, [work_schedule])
|
||||
|
||||
for inverse in file.get_inverse(work_schedule):
|
||||
if inverse.is_a("IfcRelDefinesByObject"):
|
||||
if inverse.RelatingObject == work_schedule or len(inverse.RelatedObjects) == 1:
|
||||
history = inverse.OwnerHistory
|
||||
file.remove(inverse)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(file, history)
|
||||
else:
|
||||
related_objects = list(inverse.RelatedObjects)
|
||||
related_objects.remove(work_schedule)
|
||||
inverse.RelatedObjects = related_objects
|
||||
elif inverse.is_a("IfcRelAssignsToControl"):
|
||||
[
|
||||
ifcopenshell.api.sequence.remove_task(file, task=related_object)
|
||||
for related_object in inverse.RelatedObjects
|
||||
if related_object.is_a("IfcTask")
|
||||
]
|
||||
|
||||
history = work_schedule.OwnerHistory
|
||||
file.remove(work_schedule)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(file, history)
|
||||
@@ -0,0 +1,48 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.sequence
|
||||
|
||||
|
||||
def remove_work_time(file: ifcopenshell.file, work_time: ifcopenshell.entity_instance) -> None:
|
||||
"""Removes a work time
|
||||
|
||||
:param work_time: The IfcWorkTime to remove.
|
||||
:return: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# Let's create a new calendar.
|
||||
calendar = ifcopenshell.api.sequence.add_work_calendar(model)
|
||||
|
||||
# Let's start defining the times that we work during the week.
|
||||
work_time = ifcopenshell.api.sequence.add_work_time(model,
|
||||
work_calendar=calendar, time_type="WorkingTimes")
|
||||
|
||||
# And remove it immediately
|
||||
ifcopenshell.api.sequence.remove_work_time(model, work_time=work_time)
|
||||
"""
|
||||
|
||||
# Currently in API recurrence patterns are created during assignment
|
||||
# and removed during unassignment, so they are never reused.
|
||||
if recurrence_pattern := work_time.RecurrencePattern:
|
||||
ifcopenshell.api.sequence.unassign_recurrence_pattern(file, recurrence_pattern)
|
||||
|
||||
file.remove(work_time)
|
||||
@@ -0,0 +1,62 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell.api.sequence
|
||||
|
||||
|
||||
def unassign_lag_time(file: ifcopenshell.file, rel_sequence: ifcopenshell.entity_instance) -> None:
|
||||
"""Removes any lag time in a sequence
|
||||
|
||||
The schedule is cascaded afterwards.
|
||||
|
||||
:param rel_sequence: The sequence to remove the lag time from.
|
||||
:return: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# Let's imagine we are creating a construction schedule. All tasks
|
||||
# need to be part of a work schedule.
|
||||
schedule = ifcopenshell.api.sequence.add_work_schedule(model, name="Construction Schedule A")
|
||||
|
||||
# Let's imagine a root construction task
|
||||
construction = ifcopenshell.api.sequence.add_task(model,
|
||||
work_schedule=schedule, name="Construction", identification="C")
|
||||
|
||||
# Let's imagine we're building 2 zones, one after another.
|
||||
zone1 = ifcopenshell.api.sequence.add_task(model,
|
||||
parent_task=construction, name="Zone 1", identification="C.1")
|
||||
zone2 = ifcopenshell.api.sequence.add_task(model,
|
||||
parent_task=construction, name="Zone 2", identification="C.2")
|
||||
|
||||
# Zone 1 finishes, then zone 2 starts.
|
||||
sequence = ifcopenshell.api.sequence.assign_sequence(model,
|
||||
relating_process=zone1, related_process=zone2)
|
||||
|
||||
# What if you had to wait 1 week before you could start zone 2?
|
||||
ifcopenshell.api.sequence.assign_lag_time(model, rel_sequence=sequence, lag_value="P1W")
|
||||
|
||||
# What if you didn't?
|
||||
ifcopenshell.api.sequence.unassign_lag_time(model, rel_sequence=sequence)
|
||||
"""
|
||||
if file.get_total_inverses(current_lag_time := rel_sequence.TimeLag) == 1:
|
||||
file.remove(current_lag_time)
|
||||
else:
|
||||
rel_sequence.TimeLag = None
|
||||
ifcopenshell.api.sequence.cascade_schedule(file, task=rel_sequence.RelatedProcess)
|
||||
@@ -0,0 +1,72 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.owner
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
def unassign_process(
|
||||
file: ifcopenshell.file,
|
||||
relating_process: ifcopenshell.entity_instance,
|
||||
related_object: ifcopenshell.entity_instance,
|
||||
) -> None:
|
||||
"""Unassigns a process and object relationship
|
||||
|
||||
See ifcopenshell.api.sequence.assign_process for details.
|
||||
|
||||
:param relating_process: The IfcTask in the relationship.
|
||||
:param related_object: The related object.
|
||||
:return: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# Let's imagine we are creating a construction schedule. All tasks
|
||||
# need to be part of a work schedule.
|
||||
schedule = ifcopenshell.api.sequence.add_work_schedule(model, name="Construction Schedule A")
|
||||
|
||||
# Let's create a construction task. Note that the predefined type is
|
||||
# important to distinguish types of tasks.
|
||||
task = ifcopenshell.api.sequence.add_task(model,
|
||||
work_schedule=schedule, name="Demolish existing", identification="A", predefined_type="DEMOLITION")
|
||||
|
||||
# Let's say we have a wall somewhere.
|
||||
wall = ifcopenshell.api.root.create_entity(model, ifc_class="IfcWall")
|
||||
|
||||
# Let's demolish that wall!
|
||||
ifcopenshell.api.sequence.assign_process(model, relating_process=task, related_object=wall)
|
||||
|
||||
# Change our mind.
|
||||
ifcopenshell.api.sequence.unassign_process(model, relating_process=task, related_object=wall)
|
||||
"""
|
||||
for rel in related_object.HasAssignments or []:
|
||||
if not rel.is_a("IfcRelAssignsToProcess") or rel.RelatingProcess != relating_process:
|
||||
continue
|
||||
if len(rel.RelatedObjects) == 1:
|
||||
history = rel.OwnerHistory
|
||||
file.remove(rel)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(file, history)
|
||||
return
|
||||
related_objects = list(rel.RelatedObjects)
|
||||
related_objects.remove(related_object)
|
||||
rel.RelatedObjects = related_objects
|
||||
ifcopenshell.api.owner.update_owner_history(file, element=rel)
|
||||
return rel
|
||||
@@ -0,0 +1,72 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.owner
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
def unassign_product(
|
||||
file: ifcopenshell.file,
|
||||
relating_product: ifcopenshell.entity_instance,
|
||||
related_object: ifcopenshell.entity_instance,
|
||||
) -> None:
|
||||
"""Unassigns a product and object relationship
|
||||
|
||||
See ifcopenshell.api.sequence.assign_product for details.
|
||||
|
||||
:param relating_product: The IfcProduct in the relationship.
|
||||
:param related_object: The IfcTask in the relationship.
|
||||
:return: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# Let's imagine we are creating a construction schedule. All tasks
|
||||
# need to be part of a work schedule.
|
||||
schedule = ifcopenshell.api.sequence.add_work_schedule(model, name="Construction Schedule A")
|
||||
|
||||
# Let's create a construction task. Note that the predefined type is
|
||||
# important to distinguish types of tasks.
|
||||
task = ifcopenshell.api.sequence.add_task(model,
|
||||
work_schedule=schedule, name="Build wall", identification="A", predefined_type="CONSTRUCTION")
|
||||
|
||||
# Let's say we have a wall somewhere.
|
||||
wall = ifcopenshell.api.root.create_entity(model, ifc_class="IfcWall")
|
||||
|
||||
# Let's construct that wall!
|
||||
ifcopenshell.api.sequence.assign_product(relating_product=wall, related_object=task)
|
||||
|
||||
# Change our mind.
|
||||
ifcopenshell.api.sequence.unassign_product(relating_product=wall, related_object=task)
|
||||
"""
|
||||
for rel in related_object.HasAssignments or []:
|
||||
if not rel.is_a("IfcRelAssignsToProduct") or rel.RelatingProduct != relating_product:
|
||||
continue
|
||||
if len(rel.RelatedObjects) == 1:
|
||||
history = rel.OwnerHistory
|
||||
file.remove(rel)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(file, history)
|
||||
return
|
||||
related_objects = list(rel.RelatedObjects)
|
||||
related_objects.remove(related_object)
|
||||
rel.RelatedObjects = related_objects
|
||||
ifcopenshell.api.owner.update_owner_history(file, element=rel)
|
||||
return rel
|
||||
@@ -0,0 +1,53 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
import ifcopenshell
|
||||
|
||||
|
||||
def unassign_recurrence_pattern(file: ifcopenshell.file, recurrence_pattern: ifcopenshell.entity_instance) -> None:
|
||||
"""Unassigns a recurrence pattern
|
||||
|
||||
Note that a recurring task time must have a recurrence pattern, so if
|
||||
you remove it, be sure to clean up after this API call
|
||||
(e.g. remove IfcTaskTimeRecurring entity
|
||||
or assign a different recurrence patern to it
|
||||
or replace IfcTaskTimeRecurring with IfcTaskTime).
|
||||
|
||||
:param recurrence_pattern: The IfcRecurrencePattern to remove.
|
||||
:return: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# Let's create a new calendar.
|
||||
calendar = ifcopenshell.api.sequence.add_work_calendar(model)
|
||||
|
||||
# Let's start defining the times that we work during the week.
|
||||
work_time = ifcopenshell.api.sequence.add_work_time(model,
|
||||
work_calendar=calendar, time_type="WorkingTimes")
|
||||
|
||||
# We create a weekly recurrence
|
||||
pattern = ifcopenshell.api.sequence.assign_recurrence_pattern(model,
|
||||
parent=work_time, recurrence_type="WEEKLY")
|
||||
|
||||
# Change our mind, let's just maintain it whenever we feel like it.
|
||||
ifcopenshell.api.sequence.unassign_recurrence_pattern(recurrence_pattern=pattern)
|
||||
"""
|
||||
for time_period in recurrence_pattern.TimePeriods or []:
|
||||
file.remove(time_period)
|
||||
file.remove(recurrence_pattern)
|
||||
@@ -0,0 +1,66 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.sequence
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
def unassign_sequence(
|
||||
file: ifcopenshell.file,
|
||||
relating_process: ifcopenshell.entity_instance,
|
||||
related_process: ifcopenshell.entity_instance,
|
||||
) -> None:
|
||||
"""Removes a sequence relationship between tasks
|
||||
|
||||
:param relating_process: The previous / predecessor task.
|
||||
:param related_process: The next / successor task.
|
||||
:return: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# Let's imagine we are creating a construction schedule. All tasks
|
||||
# need to be part of a work schedule.
|
||||
schedule = ifcopenshell.api.sequence.add_work_schedule(model, name="Construction Schedule A")
|
||||
|
||||
# Let's imagine a root construction task
|
||||
construction = ifcopenshell.api.sequence.add_task(model,
|
||||
work_schedule=schedule, name="Construction", identification="C")
|
||||
|
||||
# Let's imagine we're building 2 zones, one after another.
|
||||
zone1 = ifcopenshell.api.sequence.add_task(model,
|
||||
parent_task=construction, name="Zone 1", identification="C.1")
|
||||
zone2 = ifcopenshell.api.sequence.add_task(model,
|
||||
parent_task=construction, name="Zone 2", identification="C.2")
|
||||
|
||||
# Zone 1 finishes, then zone 2 starts.
|
||||
ifcopenshell.api.sequence.assign_sequence(model, relating_process=zone1, related_process=zone2)
|
||||
|
||||
# Let's make them unrelated
|
||||
ifcopenshell.api.sequence.unassign_sequence(model,
|
||||
relating_process=zone1, related_process=zone2)
|
||||
"""
|
||||
for rel in related_process.IsSuccessorFrom or []:
|
||||
if rel.RelatingProcess == relating_process:
|
||||
history = rel.OwnerHistory
|
||||
file.remove(rel)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(file, history)
|
||||
ifcopenshell.api.sequence.cascade_schedule(file, task=related_process)
|
||||
Reference in New Issue
Block a user