From 2805109cef8edb640bcad53e29ae75c1b448be5a Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 3 Jan 2023 17:31:48 +1100 Subject: [PATCH] Write documentation for sequence API module --- .../api/aggregate/assign_object.py | 2 + .../api/resource/add_resource_quantity.py | 4 +- .../api/resource/add_resource_time.py | 6 +- .../ifcopenshell/api/sequence/add_task.py | 143 ++++++++++++++++-- .../api/sequence/add_task_time.py | 53 +++++-- .../api/sequence/add_time_period.py | 59 +++++++- .../api/sequence/add_work_calendar.py | 60 +++++++- .../api/sequence/add_work_plan.py | 40 ++++- .../api/sequence/add_work_schedule.py | 53 ++++++- .../api/sequence/add_work_time.py | 54 ++++++- .../api/sequence/assign_lag_time.py | 70 ++++++++- .../api/sequence/assign_process.py | 73 ++++++++- .../api/sequence/assign_product.py | 47 +++++- .../api/sequence/assign_recurrence_pattern.py | 88 ++++++++++- .../api/sequence/assign_sequence.py | 94 ++++++++++-- .../api/sequence/assign_workplan.py | 30 +++- .../api/sequence/calculate_task_duration.py | 61 +++++++- .../api/sequence/cascade_schedule.py | 83 +++++++++- .../api/sequence/edit_lag_time.py | 57 ++++++- .../api/sequence/edit_recurrence_pattern.py | 35 ++++- .../api/sequence/edit_sequence.py | 42 ++++- .../ifcopenshell/api/sequence/edit_task.py | 32 +++- .../api/sequence/edit_task_time.py | 33 +++- .../api/sequence/edit_work_calendar.py | 27 +++- .../api/sequence/edit_work_plan.py | 27 +++- .../api/sequence/edit_work_schedule.py | 31 +++- .../api/sequence/edit_work_time.py | 32 +++- .../api/sequence/get_related_products.py | 41 ++++- .../api/sequence/recalculate_schedule.py | 28 +++- .../ifcopenshell/api/sequence/remove_task.py | 35 ++++- .../api/sequence/remove_time_period.py | 39 ++++- .../api/sequence/remove_work_calendar.py | 24 ++- .../api/sequence/remove_work_plan.py | 24 ++- .../api/sequence/remove_work_schedule.py | 27 +++- .../api/sequence/remove_work_time.py | 25 ++- .../api/sequence/unassign_lag_time.py | 41 ++++- .../api/sequence/unassign_process.py | 39 ++++- .../api/sequence/unassign_product.py | 39 ++++- .../sequence/unassign_recurrence_pattern.py | 32 +++- .../api/sequence/unassign_sequence.py | 40 ++++- 40 files changed, 1580 insertions(+), 190 deletions(-) diff --git a/src/ifcopenshell-python/ifcopenshell/api/aggregate/assign_object.py b/src/ifcopenshell-python/ifcopenshell/api/aggregate/assign_object.py index 79f8fa225a..b19a7eb983 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/aggregate/assign_object.py +++ b/src/ifcopenshell-python/ifcopenshell/api/aggregate/assign_object.py @@ -69,8 +69,10 @@ class Usecase: project = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcProject") element = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcSite") subelement = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcBuilding") + # The project contains a site (note that project aggregation is a special case in IFC) ifcopenshell.api.run("aggregate.assign_object", model, product=element, relating_object=project) + # The site has a building ifcopenshell.api.run("aggregate.assign_object", model, product=subelement, relating_object=element) """ diff --git a/src/ifcopenshell-python/ifcopenshell/api/resource/add_resource_quantity.py b/src/ifcopenshell-python/ifcopenshell/api/resource/add_resource_quantity.py index 35af0505e3..a664f7e29b 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/resource/add_resource_quantity.py +++ b/src/ifcopenshell-python/ifcopenshell/api/resource/add_resource_quantity.py @@ -54,12 +54,12 @@ class Usecase: parent_resource=crew, ifc_class="IfcLaborResource") # Labour resource is quantified in terms of time. - ifcopenshell.api.run("resource.add_resource_quantity", model, + quantity = ifcopenshell.api.run("resource.add_resource_quantity", model, resource=labour, ifc_class="IfcQuantityTime") # Store the time used in hours ifcopenshell.api.run("resource.edit_resource_quantity", model, - physical_quantity=time, attributes={"TimeValue": 8.0}) + physical_quantity=quantity, attributes={"TimeValue": 8.0}) """ self.file = file self.settings = {"resource": resource, "ifc_class": ifc_class} diff --git a/src/ifcopenshell-python/ifcopenshell/api/resource/add_resource_time.py b/src/ifcopenshell-python/ifcopenshell/api/resource/add_resource_time.py index 362c1e7160..ca63c970fb 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/resource/add_resource_time.py +++ b/src/ifcopenshell-python/ifcopenshell/api/resource/add_resource_time.py @@ -43,17 +43,17 @@ class Usecase: parent_resource=crew, ifc_class="IfcLaborResource") # Labour resource is quantified in terms of time. - ifcopenshell.api.run("resource.add_resource_quantity", model, + quantity = ifcopenshell.api.run("resource.add_resource_quantity", model, resource=labour, ifc_class="IfcQuantityTime") # Store the unit time used in hours ifcopenshell.api.run("resource.edit_resource_quantity", model, - physical_quantity=time, attributes={"TimeValue": 8.0}) + physical_quantity=quantity, attributes={"TimeValue": 8.0}) # Let's imagine we've used the resource for 2 days. time = ifcopenshell.api.run("resource.add_resource_time", model, resource=labour) ifcopenshell.api.run("resource.edit_resource_time", model, - resource_time=time, attributes={"ScheduleWork": "P16H"}) + resource_time=time, attributes={"ScheduleWork": "PT16H"}) """ self.file = file self.settings = { diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/add_task.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/add_task.py index e4c9869ca3..bb7fa42eee 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/add_task.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/add_task.py @@ -21,32 +21,149 @@ import ifcopenshell class Usecase: - def __init__(self, file, **settings): + def __init__( + self, + file, + work_schedule=None, + parent_task=None, + name=None, + description=None, + identification=None, + predefined_type="NOTDEFINED", + ): + """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. + :type work_schedule: ifcopenshell.entity_instance.entity_instance + :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. + :type parent_task: ifcopenshell.entity_instance.entity_instance + :param name: The name of the task. + :type name: str,optional + :param description: The description of the task. + :type description: str,optional + :param identification: The identification code of the task. + :type identification: str,optional + :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. + :type predefined_type: str + :return: The newly created IfcTask + :rtype: ifcopenshell.entity_instance.entity_instance + + Example:: + + # Let's imagine we are creating a construction schedule. All tasks + # need to be part of a work schedule. + schedule = ifcopenshell.api.run("sequence.add_work_schedule", model, name="Construction Schedule A") + + # Add a root task to represent the design milestones, and major + # project phases. + ifcopenshell.api.run("sequence.add_task", model, + work_schedule=schedule, name="Milestones", identification="A") + ifcopenshell.api.run("sequence.add_task", model, + work_schedule=schedule, name="Design", identification="B") + construction = ifcopenshell.api.run("sequence.add_task", model, + work_schedule=schedule, name="Construction", identification="C") + + # Let's start creating our work breakdown structure. + ifcopenshell.api.run("sequence.add_task", model, + parent_task=construction, name="Early Works", identification="C1") + ifcopenshell.api.run("sequence.add_task", model, + parent_task=construction, name="Substructure", identification="C2") + superstructure = ifcopenshell.api.run("sequence.add_task", model, + parent_task=construction, name="Superstructure", identification="C3") + + # Notice how the leaf task is the actual activity + ifcopenshell.api.run("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.run("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.run("sequence.add_task", model, + work_schedule=maintenance, name="Condenser coil cleaning") + + # These subtasks represent the punch list of maintenance tasks. + ifcopenshell.api.run("sequence.add_task", model, parent_task=cleaning, identification="1", + description="Prior to work, wear safety shoes, gloves, and goggles.") + ifcopenshell.api.run("sequence.add_task", model, parent_task=cleaning, identification="2", + description="Prepare jet pump, screwdriver, hose clamp, and control panel door key.") + ifcopenshell.api.run("sequence.add_task", model, parent_task=cleaning, identification="3", + description="Switch OFF the chiller unit.") + ifcopenshell.api.run("sequence.add_task", model, parent_task=cleaning, identification="3", + description="Open the isolator switch.") + ifcopenshell.api.run("sequence.add_task", model, parent_task=cleaning, identification="3", + description="Setup the water pressure by tapping to a water supply and connecting to a ...") + """ self.file = file self.settings = { - "work_schedule": None, - "parent_task": None, + "work_schedule": work_schedule, + "parent_task": parent_task, + "name": name, + "description": description, + "identification": identification, + "predefined_type": predefined_type, } - for key, value in settings.items(): - self.settings[key] = value def execute(self): task = ifcopenshell.api.run( "root.create_entity", self.file, ifc_class="IfcTask", - name=None, - predefined_type="NOTDEFINED", + name=self.settings["name"], + predefined_type=self.settings["predefined_type"], ) + if self.settings["description"]: + task.Description = self.settings["description"] + if self.settings["identification"]: + task.Identification = self.settings["identification"] task.IsMilestone = False if self.settings["work_schedule"]: self.file.create_entity( "IfcRelAssignsToControl", **{ "GlobalId": ifcopenshell.guid.new(), - "OwnerHistory": ifcopenshell.api.run( - "owner.create_owner_history", self.file - ), + "OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file), "RelatedObjects": [task], "RelatingControl": self.settings["work_schedule"], } @@ -59,9 +176,5 @@ class Usecase: relating_object=self.settings["parent_task"], ) if self.settings["parent_task"].Identification: - task.Identification = ( - self.settings["parent_task"].Identification - + "." - + str(len(rel.RelatedObjects)) - ) + task.Identification = self.settings["parent_task"].Identification + "." + str(len(rel.RelatedObjects)) return task diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/add_task_time.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/add_task_time.py index 13f5796a8c..06fd5674b0 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/add_task_time.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/add_task_time.py @@ -16,19 +16,54 @@ # You should have received a copy of the GNU Lesser General Public License # along with IfcOpenShell. If not, see . -import ifcopenshell.util.date - class Usecase: - def __init__(self, file, **settings): + def __init__(self, file, task=None, is_recurring=False): + """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. + :type task: ifcopenshell.entity_instance.entity_instance + :param is_recurring: Whether or not the time should recur. + :type is_recurring: bool + :return: The newly created IfcTaskTime. + :rtype: ifcopenshell.entity_instance.entity_instance + + Example:: + + # Let's imagine we are creating a construction schedule. + schedule = ifcopenshell.api.run("sequence.add_work_schedule", model, name="Construction Schedule A") + + # Create a portion of a work breakdown structure. + construction = ifcopenshell.api.run("sequence.add_task", model, + work_schedule=schedule, name="Construction", identification="C") + superstructure = ifcopenshell.api.run("sequence.add_task", model, + parent_task=construction, name="Superstructure", identification="C3") + task = ifcopenshell.api.run("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.run("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.run("sequence.edit_task_time", model, + task_time=time, attributes={"ScheduleStart": "2000-01-01", "ScheduleDuration": "P2D"}) + """ self.file = file - self.settings = { - "task": None, - } - for key, value in settings.items(): - self.settings[key] = value + self.settings = {"task": task, "is_recurring": is_recurring} def execute(self): - task_time = self.file.create_entity("IfcTaskTime") + if self.settings["is_recurring"]: + task_time = self.file.create_entity("IfcTaskTime") + else: + task_time = self.file.create_entity("IfcTaskTimeRecurring") self.settings["task"].TaskTime = task_time return task_time diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/add_time_period.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/add_time_period.py index f2bec4539e..22b9c7db42 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/add_time_period.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/add_time_period.py @@ -23,15 +23,62 @@ from datetime import timedelta class Usecase: - def __init__(self, file, **settings): + def __init__(self, file, recurrence_pattern=None, start_time=None, end_time=None): + """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. + :type recurrence_pattern: ifcopenshell.entity_instance.entity_instance + :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. + :type start_time: str,datetime.time + :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. + :type end_time: str,datetime.time + :return: The newly created IfcTimePeriod + :rtype: ifcopenshell.entity_instance.entity_instance + + Example:: + + # Let's create a new calendar. + calendar = ifcopenshell.api.run("sequence.add_work_calendar", model) + + # Let's start defining the times that we work during the week. + work_time = ifcopenshell.api.run("sequence.add_work_time", model, + work_calendar=calendar, time_type="WorkingTimes") + + # We create a weekly recurrence + pattern = ifcopenshell.api.run("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.run("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.run("sequence.add_time_period", model, + recurrence_pattern=pattern, start_time="09:00", end_time="12:00") + ifcopenshell.api.run("sequence.add_time_period", model, + recurrence_pattern=pattern, start_time="13:00", end_time="17:00") + """ self.file = file self.settings = { - "recurrence_pattern": None, - "start_time": None, - "end_time": None, + "recurrence_pattern": recurrence_pattern, + "start_time": start_time, + "end_time": end_time, } - for key, value in settings.items(): - self.settings[key] = value def execute(self): time_period = self.file.create_entity("IfcTimePeriod") diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/add_work_calendar.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/add_work_calendar.py index 04f52641d2..c1830b0f54 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/add_work_calendar.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/add_work_calendar.py @@ -20,11 +20,63 @@ import ifcopenshell.api class Usecase: - def __init__(self, file, **settings): + def __init__(self, file, name="Unnamed", predefined_type="NOTDEFINED"): + """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". + :type name: str, optional + :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 + :rtype: ifcopenshell.entity_instance.entity_instance + + Example:: + + # Let's imagine we are creating a construction schedule. All tasks + # need to be part of a work schedule. + schedule = ifcopenshell.api.run("sequence.add_work_schedule", model, name="Construction Schedule A") + + # Add a root task to represent the construction tasks. + task = ifcopenshell.api.run("sequence.add_task", model, + work_schedule=schedule, name="Construction", identification="C") + + # Let's create a new calendar. + calendar = ifcopenshell.api.run("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.run("sequence.add_work_time", model, + work_calendar=calendar, time_type="WorkingTimes") + + # We create a weekly recurrence + pattern = ifcopenshell.api.run("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.run("sequence.edit_recurrence_pattern", mdoel, + recurrence_pattern=pattern, attributes={"WeekdayComponent": [1, 2, 3, 4, 5]}) + ifcopenshell.api.run("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.run("control.assign_control", model, relating_control=calendar, related_object=task) + """ self.file = file - self.settings = {"name": "Unnamed", "predefined_type": "NOTDEFINED"} - for key, value in settings.items(): - self.settings[key] = value + self.settings = {"name": name, "predefined_type": predefined_type} def execute(self): work_calendar = ifcopenshell.api.run( diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/add_work_plan.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/add_work_plan.py index f9c6783aed..e40ae65f08 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/add_work_plan.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/add_work_plan.py @@ -22,15 +22,43 @@ from datetime import datetime class Usecase: - def __init__(self, file, **settings): + def __init__(self, file, name=None, predefined_type="NOTDEFINED", start_time=None): + """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. + :type name: str, optional + :param predefined_type: The type of work plan, used for baselining. + Leave as "NOTDEFINED" if unsure. + :type predefined_type: str + :param start_time: The earliest start time when the schedules grouped + within the work plan are relevant. + :type start_time: str,datetime.time + :return: The newly created IfcWorkPlan + :rtype: ifcopenshell.entity_instance.entity_instance + + Example:: + + # This will hold all our construction schedules + work_plan = ifcopenshell.api.run("sequence.add_work_plan", model, name="Construction") + + # This is one of our schedules in our work plan. + schedule = ifcopenshell.api.run("sequence.add_work_schedule", model, + name="Construction Schedule A", work_plan=work_plan) + """ self.file = file self.settings = { - "name": None, - "predefined_type": "NOTDEFINED", - "start_time": datetime.now(), + "name": name, + "predefined_type": predefined_type, + "start_time": start_time or datetime.now(), } - for key, value in settings.items(): - self.settings[key] = value def execute(self): work_plan = ifcopenshell.api.run( diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/add_work_schedule.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/add_work_schedule.py index f2c12da320..6adbadd8b2 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/add_work_schedule.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/add_work_schedule.py @@ -22,16 +22,55 @@ from datetime import datetime class Usecase: - def __init__(self, file, **settings): + def __init__(self, file, name="Unnamed", predefined_type="NOTDEFINED", start_time=None, work_plan=None): + """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. + :type name: str + :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. + :type predefined_type: str + :param start_time: The earlier start time when the schedule is relevant. + May be represented with an ISO standard string. + :type start_time: str,datetime.time,optional + :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. + :type work_plan: ifcopenshell.entity_instance.entity_instance,optional + :return: The newly created IfcWorkSchedule + :rtype: ifcopenshell.entity_instance.entity_instance + + Example:: + + # This will hold all our construction schedules + work_plan = ifcopenshell.api.run("sequence.add_work_plan", model, name="Construction") + + # Let's imagine this is one of our schedules in our work plan. + schedule = ifcopenshell.api.run("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.run("sequence.add_task", model, + work_schedule=schedule, name="Milestones", identification="A") + ifcopenshell.api.run("sequence.add_task", model, + work_schedule=schedule, name="Design", identification="B") + construction = ifcopenshell.api.run("sequence.add_task", model, + work_schedule=schedule, name="Construction", identification="C") + """ self.file = file self.settings = { - "name": "Unnamed", - "predefined_type": "NOTDEFINED", - "start_time": datetime.now(), - "work_plan": None, + "name": name, + "predefined_type": predefined_type, + "start_time": start_time or datetime.now(), + "work_plan": work_plan, } - for key, value in settings.items(): - self.settings[key] = value def execute(self): work_schedule = ifcopenshell.api.run( diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/add_work_time.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/add_work_time.py index f570f08c17..1996f73305 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/add_work_time.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/add_work_time.py @@ -18,11 +18,57 @@ class Usecase: - def __init__(self, file, **settings): + def __init__(self, file, work_calendar=None, time_type="WorkingTimes"): + """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. + :type work_calendar: ifcopenshell.entity_instance.entity_instance + :param time_type: Either WorkingTimes or ExceptionTimes, depending on + what you want to define. + :type time_type: str + :return: The newly created IfcWorkTime + :rtype: ifcopenshell.entity_instance.entity_instance + + Example:: + + # Let's create a new calendar. + calendar = ifcopenshell.api.run("sequence.add_work_calendar", model) + + # Let's start defining the times that we work during the week. + work_time = ifcopenshell.api.run("sequence.add_work_time", model, + work_calendar=calendar, time_type="WorkingTimes") + + # We create a weekly recurrence + pattern = ifcopenshell.api.run("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.run("sequence.edit_recurrence_pattern", model, + recurrence_pattern=pattern, attributes={"WeekdayComponent": [1, 2, 3, 4, 5]}) + + # Let's set some holidays + holidays = ifcopenshell.api.run("sequence.add_work_time", model, + work_calendar=calendar, time_type="ExceptionTimes") + + # We create a yearly recurrence + pattern = ifcopenshell.api.run("sequence.assign_recurrence_pattern", model, + parent=work_time, recurrence_type="YEARLY_BY_DAY_OF_MONTH") + + # The holiday is every 1st of January + ifcopenshell.api.run("sequence.edit_recurrence_pattern", model, + recurrence_pattern=pattern, attributes={"DayComponent": [1], "MonthComponent": [1]}) + """ self.file = file - self.settings = {"work_calendar": None, "time_type": None} - for key, value in settings.items(): - self.settings[key] = value + self.settings = {"work_calendar": work_calendar, "time_type": time_type} def execute(self): work_time = self.file.create_entity("IfcWorkTime") diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/assign_lag_time.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/assign_lag_time.py index 8789480a75..f56354ee80 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/assign_lag_time.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/assign_lag_time.py @@ -20,15 +20,73 @@ import ifcopenshell.util.date class Usecase: - def __init__(self, file, **settings): + def __init__(self, file, rel_sequence=None, lag_value=None, duration_type="WORKTIME"): + """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. + :type rel_sequence: ifcopenshell.entity_instance.entity_instance + :param lag_value: An ISO standardised duration string. + :type lag_value: str + :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. + :type duration_type: str + :return: The newly created IfcLagTime + :rtype: ifcopenshell.entity_instance.entity_instance + + Example:: + + # Let's imagine we are creating a construction schedule. All tasks + # need to be part of a work schedule. + schedule = ifcopenshell.api.run("sequence.add_work_schedule", model, name="Construction Schedule A") + + # Let's imagine a root construction task + construction = ifcopenshell.api.run("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.run("sequence.add_task", model, + parent_task=construction, name="Formwork", identification="C.1") + time = ifcopenshell.api.run("sequence.add_task_time", model, task=formwork) + ifcopenshell.api.run("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.run("sequence.add_task", model, + parent_task=construction, name="Reinforcement", identification="C.2") + time = ifcopenshell.api.run("sequence.add_task_time", model, task=reinforcement) + ifcopenshell.api.run("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.run("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.run("sequence.assign_lag_time", model, rel_sequence=sequence, lag_value="P1D") + """ self.file = file self.settings = { - "rel_sequence": None, - "lag_value": None, - "duration_type": "NOTDEFINED", + "rel_sequence": rel_sequence, + "lag_value": lag_value, + "duration_type": duration_type, } - for key, value in settings.items(): - self.settings[key] = value def execute(self): lag_value = self.file.createIfcDuration( diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/assign_process.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/assign_process.py index 375f90ee22..4fdc5276df 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/assign_process.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/assign_process.py @@ -21,21 +21,82 @@ import ifcopenshell.api class Usecase: - def __init__(self, file, **settings): + def __init__(self, file, relating_process=None, related_object=None): + """Assigns an object to be related to a process, typically a construction task + + 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. + + :param relating_process: The IfcProcess (typically IfcTask) that the + input, control, or resource is related to. + :type relating_process: ifcopenshell.entity_instance.entity_instance + :param related_object: The IfcProduct (for input), IfcCostItem (for + control) or IfcConstructionResource (for resource). + :type related_object: ifcopenshell.entity_instance.entity_instance + :return: The newly created IfcRelAssignsToProcess relationship + :rtype: ifcopenshell.entity_instance.entity_instance + + Example:: + + # Let's imagine we are creating a construction schedule. All tasks + # need to be part of a work schedule. + schedule = ifcopenshell.api.run("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.run("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.run("root.create_entity", model, ifc_class="IfcWall") + + # Let's demolish that wall! + ifcopenshell.api.run("sequence.assign_process", model, relating_process=task, related_object=wall) + """ self.file = file self.settings = { - "relating_process": None, - "related_object": None, + "relating_process": relating_process, + "related_object": related_object, } - for key, value in settings.items(): - self.settings[key] = value def execute(self): if self.settings["related_object"].HasAssignments: for assignment in self.settings["related_object"].HasAssignments: if ( - assignment.is_a("IfclRelAssignsToProcess") + assignment.is_a("IfcRelAssignsToProcess") and assignment.RelatingProcess == self.settings["relating_process"] ): return diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/assign_product.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/assign_product.py index 69b1f149a1..9400405760 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/assign_product.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/assign_product.py @@ -21,14 +21,51 @@ import ifcopenshell.api class Usecase: - def __init__(self, file, **settings): + def __init__(self, file, relating_product=None, related_object=None): + """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. + :type relating_product: ifcopenshell.entity_instance.entity_instance + :param related_object: The IfcProcess (typically IfcTask) of the + construction task. + :type related_object: ifcopenshell.entity_instance.entity_instance + :return: The newly created IfcRelAssignsToProduct relationship + :rtype: ifcopenshell.entity_instance.entity_instance + + Example:: + + # Let's imagine we are creating a construction schedule. All tasks + # need to be part of a work schedule. + schedule = ifcopenshell.api.run("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.run("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.run("root.create_entity", model, ifc_class="IfcWall") + + # Let's construct that wall! + ifcopenshell.api.run("sequence.assign_product", relating_product=wall, related_object=task) + """ self.file = file self.settings = { - "relating_product": None, - "related_object": None, + "relating_product": relating_product, + "related_object": related_object, } - for key, value in settings.items(): - self.settings[key] = value def execute(self): if self.settings["related_object"].HasAssignments: diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/assign_recurrence_pattern.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/assign_recurrence_pattern.py index 8351585dc7..ad7f8430eb 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/assign_recurrence_pattern.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/assign_recurrence_pattern.py @@ -18,11 +18,91 @@ class Usecase: - def __init__(self, file, **settings): + def __init__(self, file, parent=None, recurrence_type="WEEKLY"): + """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. + :type parent: ifcopenshell.entity_instance.entity_instance + :param recurrence_type: One of the types of recurrences. + :type recurrence_type: str + :return: The newly created IfcRecurrencePattern + :rtype: ifcopenshell.entity_instance.entity_instance + + Example:: + + # Let's create a new calendar. + calendar = ifcopenshell.api.run("sequence.add_work_calendar", model) + + # Let's start defining the times that we work during the week. + work_time = ifcopenshell.api.run("sequence.add_work_time", model, + work_calendar=calendar, time_type="WorkingTimes") + + # We create a weekly recurrence + pattern = ifcopenshell.api.run("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.run("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.run("sequence.add_work_schedule", model, name="Equipment Maintenance") + + # Now let's imagine we have a task to maintain the chillers + task = ifcopenshell.api.run("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.run("sequence.add_task_time", model, task=task, is_recurring=True) + + # We create a monthly recurrence + pattern = ifcopenshell.api.run("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.run("sequence.edit_recurrence_pattern", model, + recurrence_pattern=pattern, attributes={"DayComponent": [1], "Interval": 6}) + """ self.file = file - self.settings = {"parent": None, "recurrence_type": "WEEKLY"} - for key, value in settings.items(): - self.settings[key] = value + self.settings = {"parent": parent, "recurrence_type": recurrence_type} def execute(self): recurrence = self.file.createIfcRecurrencePattern( diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/assign_sequence.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/assign_sequence.py index 5866ccf9bb..0abac75830 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/assign_sequence.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/assign_sequence.py @@ -21,14 +21,92 @@ import ifcopenshell.api class Usecase: - def __init__(self, file, **settings): + def __init__(self, file, relating_process=None, related_process=None, sequence_type="FINISH_START"): + """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. + :type relating_process: ifcopenshell.entity_instance.entity_instance + :param related_process: The next / successor task. + :type related_process: ifcopenshell.entity_instance.entity_instance + :param sequence_type: Choose from FINISH_START, FINISH_FINISH, + START_START, or START_FINISH. + :return: The newly created IfcRelSequence + :rtype: ifcopenshell.entity_instance.entity_instance + + Example:: + + # Let's imagine we are creating a construction schedule. All tasks + # need to be part of a work schedule. + schedule = ifcopenshell.api.run("sequence.add_work_schedule", model, name="Construction Schedule A") + + # Let's imagine a root construction task + construction = ifcopenshell.api.run("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.run("sequence.add_task", model, + parent_task=construction, name="Formwork", identification="C.1") + time = ifcopenshell.api.run("sequence.add_task_time", model, task=formwork) + ifcopenshell.api.run("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.run("sequence.add_task", model, + parent_task=construction, name="Reinforcement", identification="C.2") + time = ifcopenshell.api.run("sequence.add_task_time", model, task=reinforcement) + ifcopenshell.api.run("sequence.edit_task_time", model, + task_time=time, attributes={"ScheduleStart": "2000-01-01", "ScheduleDuration": "P2D"}) + + # Now the pour itself. It'll only take 1 day. + pour = ifcopenshell.api.run("sequence.add_task", model, + parent_task=construction, name="Reinforcement", identification="C.3") + time = ifcopenshell.api.run("sequence.add_task_time", model, task=pour) + ifcopenshell.api.run("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.run("sequence.assign_sequence", model, + relating_process=formwork, related_process=reinforcement) + ifcopenshell.api.run("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.run("sequence.cascade_schedule", model, task=formwork) + """ self.file = file self.settings = { - "relating_process": None, - "related_process": None, + "relating_process": relating_process, + "related_process": related_process, + "sequence_type": sequence_type, } - for key, value in settings.items(): - self.settings[key] = value def execute(self): for rel in self.settings["related_process"].IsSuccessorFrom or []: @@ -38,11 +116,9 @@ class Usecase: "IfcRelSequence", **{ "GlobalId": ifcopenshell.guid.new(), - "OwnerHistory": ifcopenshell.api.run( - "owner.create_owner_history", self.file - ), + "OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file), "RelatingProcess": self.settings["relating_process"], "RelatedProcess": self.settings["related_process"], - "SequenceType": "FINISH_START", + "SequenceType": self.settings["sequence_type"], } ) diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/assign_workplan.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/assign_workplan.py index 471cfc4b16..ac0b46beea 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/assign_workplan.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/assign_workplan.py @@ -21,11 +21,33 @@ import ifcopenshell.api class Usecase: - def __init__(self, file, **settings): + def __init__(self, file, work_schedule=None, work_plan=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. + :type work_schedule: ifcopenshell.entity_instance.entity_instance + :param work_plan: The IfcWorkPlan for the schedule to be assigned to. + :type work_plan: ifcopenshell.entity_instance.entity_instance + :return: The IfcRelAggregates relationship + :rtype: ifcopenshell.entity_instance.entity_instance + + Example:: + + # This will hold all our construction schedules + work_plan = ifcopenshell.api.run("sequence.add_work_plan", model, name="Construction") + + # Alternatively, if you create a schedule without a work plan ... + schedule = ifcopenshell.api.run("sequence.add_work_schedule", model, name="Construction Schedule A") + + # ... you can assign the work plan afterwards. + ifcopenshell.api.run("sequence.assign_workplan", work_schedule=schedule, work_plan=work_plan) + """ self.file = file - self.settings = {"work_schedule": None, "work_plan": None} - for key, value in settings.items(): - self.settings[key] = value + self.settings = {"work_schedule": work_schedule, "work_plan": work_plan} def execute(self): # TODO: this is an ambiguity by buildingSMART diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/calculate_task_duration.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/calculate_task_duration.py index 6e7c1b1aff..b974bf3f0b 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/calculate_task_duration.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/calculate_task_duration.py @@ -23,11 +23,64 @@ import ifcopenshell.util.element class Usecase: - def __init__(self, file, **settings): + def __init__(self, file, task=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. + :type task: ifcopenshell.entity_instance.entity_instance + :return: None + :rtype: None + + Example:: + + # Add our own crew + crew = ifcopenshell.api.run("resource.add_resource", model, ifc_class="IfcCrewResource") + + # Add some labour to our crew. + labour = ifcopenshell.api.run("resource.add_resource", model, + parent_resource=crew, ifc_class="IfcLaborResource") + + # Labour resource is quantified in terms of time. + quantity = ifcopenshell.api.run("resource.add_resource_quantity", model, + resource=labour, ifc_class="IfcQuantityTime") + + # Store the unit time used in hours + ifcopenshell.api.run("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.run("resource.add_resource_time", model, resource=labour) + ifcopenshell.api.run("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.run("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.run("sequence.add_task", model, + work_schedule=schedule, name="Foundations", identification="A") + + # Assign our resource to the task. + ifcopenshell.api.run("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.run("sequence.calculate_task_duration", model, task=task) + """ self.file = file - self.settings = {"task": None} - for key, value in settings.items(): - self.settings[key] = value + self.settings = {"task": task} def execute(self): self.seconds_per_workday = self.calculate_seconds_per_workday() diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/cascade_schedule.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/cascade_schedule.py index 4462eb2f86..8101c8badb 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/cascade_schedule.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/cascade_schedule.py @@ -22,11 +22,86 @@ import ifcopenshell.util.sequence class Usecase: - def __init__(self, file, **settings): + def __init__(self, file, task=None): + """Cascades start and end dates of tasks based on durations + + Given a start task with a start date and duration, the end date, and the + start and end of all successor tasks with durations may be automatically + computed. + + Using this automatic computation is recommended is an alternative to + manually specifying dates. It is useful for doing edits and cascading + changes. + + Dates can only cascade from predecessor to successors, not backwards. + Cyclical relationships are invalid and will result in a recursion error + being raised. + + Note that there may be differences between how different planning + software calculate start and end dates. Some may consider Monday 5pm to + be equivalent to be Tuesday 8am, for instance. + + :param task: The start task to begin cascading from. + :type task: ifcopenshell.entity_instance.entity_instance + :return: None + :rtype: None + + Example:: + + # Define a convenience function to add a task chained to a predecessor + def add_task(model, name, predecessor, work_schedule): + # Add a construction task + task = ifcopenshell.api.run("sequence.add_task", model, + work_schedule=work_schedule, name=name, predefined_type="CONSTRUCTION") + + # Give it a time + task_time = ifcopenshell.api.run("sequence.add_task_time", model, task=task) + + # Arbitrarily set the task's scheduled time duration to be 1 week + ifcopenshell.api.run("sequence.edit_task_time", model, task_time=task_time, + attributes={"ScheduleStart": datetime.date(2000, 1, 1), "ScheduleDuration": "P1W"}) + + # If a predecessor exists, create a finish to start relationship + if predecessor: + ifcopenshell.api.run("sequence.assign_sequence", model, + relating_process=predecessor, related_process=task) + + return task + + # Open an existing IFC4 model you have of a building + model = ifcopenshell.open("/path/to/existing/model.ifc") + + # Create a new construction schedule + schedule = ifcopenshell.api.run("sequence.add_work_schedule", model, name="Construction") + + # Let's imagine a starting task for site establishment. + task = add_task(model, "Site establishment", None, schedule) + start_task = task + + # Get all our storeys sorted by elevation ascending. + storeys = sorted(model.by_type("IfcBuildingStorey"), key=lambda s: get_storey_elevation(s)) + + # For each storey ... + for storey in storeys: + + # Add a construction task to construct that storey, using our convenience function + task = add_task(model, f"Construct {storey.Name}", task, schedule) + + # Assign all the products in that storey to the task as construction outputs. + for product in get_decomposition(storey): + ifcopenshell.api.run("sequence.assign_product", model, relating_product=product, related_object=task) + + # Ask the computer to calculate all the dates for us from the start task. + # For example, if the first task started on the 1st of January and took a + # week, the next task will start on the 8th of January. This saves us + # manually doing date calculations. + ifcopenshell.api.run("sequence.cascade_schedule", model, task=start_task) + + # Calculate the critical path and floats. + ifcopenshell.api.run("sequence.recalculate_schedule", model, work_schedule=schedule) + """ self.file = file - self.settings = {"task": None} - for key, value in settings.items(): - self.settings[key] = value + self.settings = {"task": task} def execute(self): self.calendar_cache = {} diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_lag_time.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_lag_time.py index 756bb5c56d..568809de53 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_lag_time.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_lag_time.py @@ -21,11 +21,60 @@ import ifcopenshell.util.date class Usecase: - def __init__(self, file, **settings): + def __init__(self, file, lag_time=None, attributes=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 + :type lag_time: ifcopenshell.entity_instance.entity_instance + :param attributes: a dictionary of attribute names and values. + :type attributes: dict, optional + :return: None + :rtype: None + + Example:: + + # Let's imagine we are creating a construction schedule. All tasks + # need to be part of a work schedule. + schedule = ifcopenshell.api.run("sequence.add_work_schedule", model, name="Construction Schedule A") + + # Let's imagine a root construction task + construction = ifcopenshell.api.run("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.run("sequence.add_task", model, + parent_task=construction, name="Formwork", identification="C.1") + time = ifcopenshell.api.run("sequence.add_task_time", model, task=formwork) + ifcopenshell.api.run("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.run("sequence.add_task", model, + parent_task=construction, name="Reinforcement", identification="C.2") + time = ifcopenshell.api.run("sequence.add_task_time", model, task=reinforcement) + ifcopenshell.api.run("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.run("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.run("sequence.assign_lag_time", model, rel_sequence=sequence, lag_value="P1D") + + # Or, let's make it 2 days instead. + ifcopenshell.api.run("sequence.edit_lag_time", model, lag_time=lag, attributes={"LagValue": "P2D"}) + """ self.file = file - self.settings = {"lag_time": None, "attributes": {}} - for key, value in settings.items(): - self.settings[key] = value + self.settings = {"lag_time": lag_time, "attributes": attributes or {}} def execute(self): for name, value in self.settings["attributes"].items(): diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_recurrence_pattern.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_recurrence_pattern.py index 152f7d1b71..c34b14208a 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_recurrence_pattern.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_recurrence_pattern.py @@ -18,11 +18,38 @@ class Usecase: - def __init__(self, file, **settings): + def __init__(self, file, recurrence_pattern=None, attributes=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 + :type recurrence_pattern: ifcopenshell.entity_instance.entity_instance + :param attributes: a dictionary of attribute names and values. + :type attributes: dict, optional + :return: None + :rtype: None + + Example:: + + # Let's create a new calendar. + calendar = ifcopenshell.api.run("sequence.add_work_calendar", model) + + # Let's start defining the times that we work during the week. + work_time = ifcopenshell.api.run("sequence.add_work_time", model, + work_calendar=calendar, time_type="WorkingTimes") + + # We create a weekly recurrence + pattern = ifcopenshell.api.run("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.run("sequence.edit_recurrence_pattern", model, + recurrence_pattern=pattern, attributes={"WeekdayComponent": [1, 2, 3, 4, 5]}) + """ self.file = file - self.settings = {"recurrence_pattern": None, "attributes": {}} - for key, value in settings.items(): - self.settings[key] = value + self.settings = {"recurrence_pattern": recurrence_pattern, "attributes": attributes or {}} def execute(self): for name, value in self.settings["attributes"].items(): diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_sequence.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_sequence.py index 4da730d94c..2edeed267f 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_sequence.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_sequence.py @@ -21,11 +21,45 @@ import ifcopenshell.api class Usecase: - def __init__(self, file, **settings): + def __init__(self, file, rel_sequence=None, attributes=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 + :type rel_sequence: ifcopenshell.entity_instance.entity_instance + :param attributes: a dictionary of attribute names and values. + :type attributes: dict, optional + :return: None + :rtype: None + + Example:: + + # Let's imagine we are creating a construction schedule. All tasks + # need to be part of a work schedule. + schedule = ifcopenshell.api.run("sequence.add_work_schedule", model, name="Construction Schedule A") + + # Let's imagine a root construction task + construction = ifcopenshell.api.run("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.run("sequence.add_task", model, + parent_task=construction, name="Zone 1", identification="C.1") + zone2 = ifcopenshell.api.run("sequence.add_task", model, + parent_task=construction, name="Zone 2", identification="C.2") + + # Zone 1 finishes, then zone 2 starts. + sequence = ifcopenshell.api.run("sequence.assign_sequence", model, + relating_process=zone1, related_process=zone2) + + # What if they both started at the same time? + ifcopenshell.api.run("sequence.edit_sequence", model, + rel_sequence=sequence, attributes={"SequenceType": "START_START"}) + """ self.file = file - self.settings = {"rel_sequence": None, "attributes": {}} - for key, value in settings.items(): - self.settings[key] = value + self.settings = {"rel_sequence": rel_sequence, "attributes": attributes or {}} def execute(self): for name, value in self.settings["attributes"].items(): diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_task.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_task.py index 2d26721571..ce7d1b3b7f 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_task.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_task.py @@ -18,11 +18,35 @@ class Usecase: - def __init__(self, file, **settings): + def __init__(self, file, task=None, attributes=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 + :type task: ifcopenshell.entity_instance.entity_instance + :param attributes: a dictionary of attribute names and values. + :type attributes: dict, optional + :return: None + :rtype: None + + Example:: + + # Let's imagine we are creating a construction schedule. All tasks + # need to be part of a work schedule. + schedule = ifcopenshell.api.run("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.run("sequence.add_task", model, + work_schedule=schedule, name="Milestones", identification="A") + + # Change the identification + ifcopenshell.api.run("sequence.edit_task", model, task=task, attributes={"Identification": "M"}) + """ self.file = file - self.settings = {"task": None, "attributes": {}} - for key, value in settings.items(): - self.settings[key] = value + self.settings = {"task": task, "attributes": attributes or {}} def execute(self): for name, value in self.settings["attributes"].items(): diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_task_time.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_task_time.py index 8a3c8b35d6..355a357195 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_task_time.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_task_time.py @@ -22,11 +22,36 @@ import ifcopenshell.util.sequence class Usecase: - def __init__(self, file, **settings): + def __init__(self, file, task_time=None, attributes=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 + :type task_time: ifcopenshell.entity_instance.entity_instance + :param attributes: a dictionary of attribute names and values. + :type attributes: dict, optional + :return: None + :rtype: None + + Example:: + + # Let's imagine we are creating a construction schedule. All tasks + # need to be part of a work schedule. + schedule = ifcopenshell.api.run("sequence.add_work_schedule", model, name="Construction Schedule A") + + # Create a task to do formwork + task = ifcopenshell.api.run("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.run("sequence.add_task_time", model, task=formwork) + ifcopenshell.api.run("sequence.edit_task_time", model, + task_time=time, attributes={"ScheduleStart": "2000-01-01", "ScheduleDuration": "P2D"}) + """ self.file = file - self.settings = {"task_time": None, "attributes": {}} - for key, value in settings.items(): - self.settings[key] = value + self.settings = {"task_time": task_time, "attributes": attributes or {}} def execute(self): self.task = self.get_task() diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_work_calendar.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_work_calendar.py index 73f1743296..e9b4597c98 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_work_calendar.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_work_calendar.py @@ -18,11 +18,30 @@ class Usecase: - def __init__(self, file, **settings): + def __init__(self, file, work_calendar=None, attributes=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 + :type work_calendar: ifcopenshell.entity_instance.entity_instance + :param attributes: a dictionary of attribute names and values. + :type attributes: dict, optional + :return: None + :rtype: None + + Example:: + + # Let's create a new calendar. + calendar = ifcopenshell.api.run("sequence.add_work_calendar", model, name="5 Day Week") + + # Let's give it a description + ifcopenshell.api.run("sequence.edit_work_calendar", model, + work_calendar=calendar, attributes={"Description": "Monday to Friday 8 hour days"}) + """ self.file = file - self.settings = {"work_calendar": None, "attributes": {}} - for key, value in settings.items(): - self.settings[key] = value + self.settings = {"work_calendar": work_calendar, "attributes": attributes or {}} def execute(self): for name, value in self.settings["attributes"].items(): diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_work_plan.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_work_plan.py index 38bec37b5e..8873b8d8ef 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_work_plan.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_work_plan.py @@ -20,11 +20,30 @@ import ifcopenshell.util.date class Usecase: - def __init__(self, file, **settings): + def __init__(self, file, work_plan=None, attributes=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 + :type work_plan: ifcopenshell.entity_instance.entity_instance + :param attributes: a dictionary of attribute names and values. + :type attributes: dict, optional + :return: None + :rtype: None + + Example:: + + # This will hold all our construction schedules + work_plan = ifcopenshell.api.run("sequence.add_work_plan", model, name="Construction") + + # Let's give it a description + ifcopenshell.api.run("sequence.edit_work_plan", model, + work_plan=work_plan, attributes={"Description": "Construction of phase 1"}) + """ self.file = file - self.settings = {"work_plan": None, "attributes": {}} - for key, value in settings.items(): - self.settings[key] = value + self.settings = {"work_plan": work_plan, "attributes": attributes or {}} def execute(self): for name, value in self.settings["attributes"].items(): diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_work_schedule.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_work_schedule.py index 911e88bc08..4e81dca996 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_work_schedule.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_work_schedule.py @@ -20,11 +20,34 @@ import ifcopenshell.util.date class Usecase: - def __init__(self, file, **settings): + def __init__(self, file, work_schedule=None, attributes=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 + :type work_schedule: ifcopenshell.entity_instance.entity_instance + :param attributes: a dictionary of attribute names and values. + :type attributes: dict, optional + :return: None + :rtype: None + + Example:: + + # This will hold all our construction schedules + work_plan = ifcopenshell.api.run("sequence.add_work_plan", model, name="Construction") + + # Let's imagine this is one of our schedules in our work plan. + schedule = ifcopenshell.api.run("sequence.add_work_schedule", model, + name="Construction Schedule A", work_plan=work_plan) + + # Let's give it a description + ifcopenshell.api.run("sequence.edit_work_schedule", model, + work_schedule=work_schedule, attributes={"Description": "3 crane design option"}) + """ self.file = file - self.settings = {"work_schedule": None, "attributes": {}} - for key, value in settings.items(): - self.settings[key] = value + self.settings = {"work_schedule": work_schedule, "attributes": attributes or {}} def execute(self): for name, value in self.settings["attributes"].items(): diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_work_time.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_work_time.py index 46168f0b25..3bfafc551e 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_work_time.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/edit_work_time.py @@ -20,11 +20,35 @@ import ifcopenshell.util.date class Usecase: - def __init__(self, file, **settings): + def __init__(self, file, work_time=None, attributes=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 + :type work_time: ifcopenshell.entity_instance.entity_instance + :param attributes: a dictionary of attribute names and values. + :type attributes: dict, optional + :return: None + :rtype: None + + Example:: + + # Let's create a new calendar. + calendar = ifcopenshell.api.run("sequence.add_work_calendar", model) + + # Let's start defining the times that we work during the week. + work_time = ifcopenshell.api.run("sequence.add_work_time", model, + work_calendar=calendar, time_type="WorkingTimes") + + # Edit our work time to state that the work time only applies after + # a start date. + ifcopenshell.api.run("sequence.edit_work_time", model, + work_time=work_time, attributes={"StartDate": "2000-01-01"}) + """ self.file = file - self.settings = {"work_time": None, "attributes": {}} - for key, value in settings.items(): - self.settings[key] = value + self.settings = {"work_time": work_time, "attributes": attributes or {}} def execute(self): for name, value in self.settings["attributes"].items(): diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/get_related_products.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/get_related_products.py index 46de034aaf..db9cc3c48e 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/get_related_products.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/get_related_products.py @@ -20,14 +20,45 @@ import ifcopenshell class Usecase: - def __init__(self, file, **settings): + def __init__(self, file, relating_product=None, related_object=None): + """Gets the related products being output by a task + + This API function will be removed in the future and migrated to a + utility module. + + :param relating_product: One of the products already output by the task. + :type relating_product: ifcopenshell.entity_instance.entity_instance + :param related_object: The IfcTask that you want to get all the related + products for. + :type related_object: ifcopenshell.entity_instance.entity_instance + :return: A set of IfcProducts output by the IfcTask. + :rtype: set[ifcopenshell.entity_instance.entity_instance] + + Example:: + + # Let's imagine we are creating a construction schedule. All tasks + # need to be part of a work schedule. + schedule = ifcopenshell.api.run("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.run("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.run("root.create_entity", model, ifc_class="IfcWall") + + # Let's construct that wall! + ifcopenshell.api.run("sequence.assign_product", relating_product=wall, related_object=task) + + # This will give us a set with that wall in it. + products = ifcopenshell.api.run("sequence.get_related_products", model, related_object=task) + """ self.file = file self.settings = { - "relating_product": None, - "related_object": None, + "relating_product": relating_product, + "related_object": related_object, } - for key, value in settings.items(): - self.settings[key] = value def execute(self): products = set() diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/recalculate_schedule.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/recalculate_schedule.py index a1449d2a0e..8e6adfcf22 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/recalculate_schedule.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/recalculate_schedule.py @@ -24,11 +24,31 @@ import ifcopenshell.util.sequence class Usecase: - def __init__(self, file, **settings): + def __init__(self, file, work_schedule=None): + """Calculate the critical path and floats for a work schedule + + This implements critical path analysis, using the forward pass and + backward pass method. When run, any tasks that have no float will be + marked as critical, and both the total and free floats will be + populated for all task times. + + Cyclical relationships are detected and will result in a recursion + error. + + :param work_schedule: The IfcWorkSchedule to perform the calculation on. + :type work_schedule: ifcopenshell.entity_instance.entity_instance + :return: None + :rtype: None + + Example:: + + # See the example for ifcopenshell.api.sequence.cascade_schedule for + # details of how to set up a basic set of tasks and calculate the + # critical path. Typically cascade_schedule is run prior to ensure + # that dates are correct. + """ self.file = file - self.settings = {"work_schedule": None} - for key, value in settings.items(): - self.settings[key] = value + self.settings = {"work_schedule": work_schedule} def execute(self): # The method implemented is the same as shown here: diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_task.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_task.py index 4c6b66cc68..da937336fb 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_task.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_task.py @@ -20,11 +20,38 @@ import ifcopenshell.api class Usecase: - def __init__(self, file, **settings): + def __init__(self, file, task=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. + :type task: ifcopenshell.entity_instance.entity_instance + :return: None + :rtype: None + + Example:: + + # Let's imagine we are creating a construction schedule. All tasks + # need to be part of a work schedule. + schedule = ifcopenshell.api.run("sequence.add_work_schedule", model, name="Construction Schedule A") + + # Add a root task to represent the design milestones, and major + # project phases. + ifcopenshell.api.run("sequence.add_task", model, + work_schedule=schedule, name="Milestones", identification="A") + design = ifcopenshell.api.run("sequence.add_task", model, + work_schedule=schedule, name="Design", identification="B") + ifcopenshell.api.run("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.run("sequence.remove_task", model, task=design) + """ self.file = file - self.settings = {"task": None} - for key, value in settings.items(): - self.settings[key] = value + self.settings = {"task": task} def execute(self): # TODO: do a deep purge diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_time_period.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_time_period.py index 8094496035..d1c4e3c0a6 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_time_period.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_time_period.py @@ -20,11 +20,42 @@ import ifcopenshell.api class Usecase: - def __init__(self, file, **settings): + def __init__(self, file, time_period=None): + """Removes a time period + + :param time_period: The IfcTimePeriod to remove. + :type time_period: ifcopenshell.entity_instance.entity_instance + :return: None + :rtype: None + + Example:: + + # Let's create a new calendar. + calendar = ifcopenshell.api.run("sequence.add_work_calendar", model) + + # Let's start defining the times that we work during the week. + work_time = ifcopenshell.api.run("sequence.add_work_time", model, + work_calendar=calendar, time_type="WorkingTimes") + + # We create a weekly recurrence + pattern = ifcopenshell.api.run("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.run("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.run("sequence.add_time_period", model, + recurrence_pattern=pattern, start_time="09:00", end_time="12:00") + afternoon = ifcopenshell.api.run("sequence.add_time_period", model, + recurrence_pattern=pattern, start_time="13:00", end_time="17:00") + + # Let's take the afternoon off! + ifcopenshell.api.run("sequence.remove_time_period", model, time_period=afternoon) + """ self.file = file - self.settings = {"time_period": None} - for key, value in settings.items(): - self.settings[key] = value + self.settings = {"time_period": time_period} def execute(self): self.file.remove(self.settings["time_period"]) diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_work_calendar.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_work_calendar.py index 4ee9c52569..a2e7e22899 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_work_calendar.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_work_calendar.py @@ -20,11 +20,27 @@ import ifcopenshell class Usecase: - def __init__(self, file, **settings): + def __init__(self, file, work_calendar=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 + :type work_calendar: ifcopenshell.entity_instance.entity_instance + :return: None + :rtype: None + + Example:: + + # Let's create a new calendar. + calendar = ifcopenshell.api.run("sequence.add_work_calendar", model, name="5 Day Week") + + # And remove it immediately + ifcopenshell.api.run("sequence.remove_work_calendar", model, work_calendar=calendar) + """ self.file = file - self.settings = {"work_calendar": None} - for key, value in settings.items(): - self.settings[key] = value + self.settings = {"work_calendar": work_calendar} def execute(self): # TODO: do a deep purge diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_work_plan.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_work_plan.py index 647a19fce6..7f23d4be6b 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_work_plan.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_work_plan.py @@ -20,11 +20,27 @@ import ifcopenshell class Usecase: - def __init__(self, file, **settings): + def __init__(self, file, work_plan=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. + :type work_plan: ifcopenshell.entity_instance.entity_instance + :return: None + :rtype: None + + Example:: + + # This will hold all our construction schedules + work_plan = ifcopenshell.api.run("sequence.add_work_plan", model, name="Construction") + + # And remove it immediately + ifcopenshell.api.run("sequence.remove_work_plan", model, work_plan=work_plan) + """ self.file = file - self.settings = {"work_plan": None} - for key, value in settings.items(): - self.settings[key] = value + self.settings = {"work_plan": work_plan} def execute(self): # TODO: do a deep purge diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_work_schedule.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_work_schedule.py index abe163252b..d6c956dc63 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_work_schedule.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_work_schedule.py @@ -20,11 +20,30 @@ import ifcopenshell.api class Usecase: - def __init__(self, file, **settings): + def __init__(self, file, work_schedule=None): + """Removes a work schedule + + All tasks in the work schedule are also removed recursively. + + :param work_schedule: The IfcWorkSchedule to remove. + :type work_schedule: ifcopenshell.entity_instance.entity_instance + :return: None + :rtype: None + + Example:: + + # This will hold all our construction schedules + work_plan = ifcopenshell.api.run("sequence.add_work_plan", model, name="Construction") + + # Let's imagine this is one of our schedules in our work plan. + schedule = ifcopenshell.api.run("sequence.add_work_schedule", model, + name="Construction Schedule A", work_plan=work_plan) + + # And remove it immediately + ifcopenshell.api.run("sequence.remove_work_schedule", model, work_schedule=schedule) + """ self.file = file - self.settings = {"work_schedule": None} - for key, value in settings.items(): - self.settings[key] = value + self.settings = {"work_schedule": work_schedule} def execute(self): # TODO: do a deep purge diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_work_time.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_work_time.py index 99d6fe1804..305a6da192 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_work_time.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/remove_work_time.py @@ -18,11 +18,28 @@ class Usecase: - def __init__(self, file, **settings): + def __init__(self, file, work_time=None): + """Removes a work time + + :param work_time: The IfcWorkTime to remove. + :type work_time: ifcopenshell.entity_instance.entity_instance + :return: None + :rtype: None + + Example:: + + # Let's create a new calendar. + calendar = ifcopenshell.api.run("sequence.add_work_calendar", model) + + # Let's start defining the times that we work during the week. + work_time = ifcopenshell.api.run("sequence.add_work_time", model, + work_calendar=calendar, time_type="WorkingTimes") + + # And remove it immediately + ifcopenshell.api.run("sequence.remove_work_time", model, work_time=work_time) + """ self.file = file - self.settings = {"work_time": None} - for key, value in settings.items(): - self.settings[key] = value + self.settings = {"work_time": work_time} def execute(self): self.file.remove(self.settings["work_time"]) diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/unassign_lag_time.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/unassign_lag_time.py index 0509658f62..85972fd418 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/unassign_lag_time.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/unassign_lag_time.py @@ -20,13 +20,46 @@ import ifcopenshell.api class Usecase: - def __init__(self, file, **settings): + def __init__(self, file, rel_sequence=None): + """Removes any lag time in a sequence + + The schedule is cascaded afterwards. + + :param rel_sequence: The sequence to remove the lag time from. + :type rel_sequence: ifcopenshell.entity_instance.entity_instance + :return: None + :rtype: None + + Example:: + + # Let's imagine we are creating a construction schedule. All tasks + # need to be part of a work schedule. + schedule = ifcopenshell.api.run("sequence.add_work_schedule", model, name="Construction Schedule A") + + # Let's imagine a root construction task + construction = ifcopenshell.api.run("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.run("sequence.add_task", model, + parent_task=construction, name="Zone 1", identification="C.1") + zone2 = ifcopenshell.api.run("sequence.add_task", model, + parent_task=construction, name="Zone 2", identification="C.2") + + # Zone 1 finishes, then zone 2 starts. + sequence = ifcopenshell.api.run("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.run("sequence.assign_lag_time", model, rel_sequence=sequence, lag_value="P1W") + + # What if you didn't? + ifcopenshell.api.run("sequence.unassign_lag_time", model, rel_sequence=sequence) + """ self.file = file self.settings = { - "rel_sequence": None, + "rel_sequence": rel_sequence, } - for key, value in settings.items(): - self.settings[key] = value def execute(self): if len(self.file.get_inverse(self.settings["rel_sequence"].TimeLag)) == 1: diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/unassign_process.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/unassign_process.py index a84af39dcb..dd8e71dac7 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/unassign_process.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/unassign_process.py @@ -21,14 +21,43 @@ import ifcopenshell.api class Usecase: - def __init__(self, file, **settings): + def __init__(self, file, relating_process=None, related_object=None): + """Unassigns a process and object relationship + + See ifcopenshell.api.sequence.assign_process for details. + + :param relating_process: The IfcTask in the relationship. + :type relating_process: ifcopenshell.entity_instance.entity_instance + :param related_object: The related object. + :type related_object: ifcopenshell.entity_instance.entity_instance + :return: None + :rtype: None + + Example:: + + # Let's imagine we are creating a construction schedule. All tasks + # need to be part of a work schedule. + schedule = ifcopenshell.api.run("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.run("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.run("root.create_entity", model, ifc_class="IfcWall") + + # Let's demolish that wall! + ifcopenshell.api.run("sequence.assign_process", model, relating_process=task, related_object=wall) + + # Change our mind. + ifcopenshell.api.run("sequence.unassign_process", model, relating_process=task, related_object=wall) + """ self.file = file self.settings = { - "relating_process": None, - "related_object": None, + "relating_process": relating_process, + "related_object": related_object, } - for key, value in settings.items(): - self.settings[key] = value def execute(self): for rel in self.settings["related_object"].HasAssignments or []: diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/unassign_product.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/unassign_product.py index 81d74a74c1..e23257b160 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/unassign_product.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/unassign_product.py @@ -21,14 +21,43 @@ import ifcopenshell.api class Usecase: - def __init__(self, file, **settings): + def __init__(self, file, relating_product=None, related_object=None): + """Unassigns a product and object relationship + + See ifcopenshell.api.sequence.assign_product for details. + + :param relating_product: The IfcProduct in the relationship. + :type relating_product: ifcopenshell.entity_instance.entity_instance + :param related_object: The IfcTask in the relationship. + :type related_object: ifcopenshell.entity_instance.entity_instance + :return: None + :rtype: None + + Example:: + + # Let's imagine we are creating a construction schedule. All tasks + # need to be part of a work schedule. + schedule = ifcopenshell.api.run("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.run("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.run("root.create_entity", model, ifc_class="IfcWall") + + # Let's construct that wall! + ifcopenshell.api.run("sequence.assign_product", relating_product=wall, related_object=task) + + # Change our mind. + ifcopenshell.api.run("sequence.unassign_product", relating_product=wall, related_object=task) + """ self.file = file self.settings = { - "relating_product": None, - "related_object": None, + "relating_product": relating_product, + "related_object": related_object, } - for key, value in settings.items(): - self.settings[key] = value def execute(self): for rel in self.settings["related_object"].HasAssignments or []: diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/unassign_recurrence_pattern.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/unassign_recurrence_pattern.py index 59cac5fed8..1dbb149b4d 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/unassign_recurrence_pattern.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/unassign_recurrence_pattern.py @@ -20,11 +20,35 @@ import ifcopenshell.api class Usecase: - def __init__(self, file, **settings): + def __init__(self, file, recurrence_pattern=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 yourself. + + :param recurrence_pattern: The IfcRecurrencePattern to remove. + :type recurrence_pattern: ifcopenshell.entity_instance.entity_instance + :return: None + :rtype: None + + Example:: + + # Let's create a new calendar. + calendar = ifcopenshell.api.run("sequence.add_work_calendar", model) + + # Let's start defining the times that we work during the week. + work_time = ifcopenshell.api.run("sequence.add_work_time", model, + work_calendar=calendar, time_type="WorkingTimes") + + # We create a weekly recurrence + pattern = ifcopenshell.api.run("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.run("sequence.unassign_recurrence_pattern", recurrence_pattern=pattern) + """ self.file = file - self.settings = {"recurrence_pattern": None} - for key, value in settings.items(): - self.settings[key] = value + self.settings = {"recurrence_pattern": recurrence_pattern} def execute(self): self.file.remove(self.settings["recurrence_pattern"]) diff --git a/src/ifcopenshell-python/ifcopenshell/api/sequence/unassign_sequence.py b/src/ifcopenshell-python/ifcopenshell/api/sequence/unassign_sequence.py index 0649e686dd..f12c36fd4d 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/sequence/unassign_sequence.py +++ b/src/ifcopenshell-python/ifcopenshell/api/sequence/unassign_sequence.py @@ -21,14 +21,44 @@ import ifcopenshell.api class Usecase: - def __init__(self, file, **settings): + def __init__(self, file, relating_process=None, related_process=None): + """Removes a sequence relationship between tasks + + :param relating_process: The previous / predecessor task. + :type relating_process: ifcopenshell.entity_instance.entity_instance + :param related_process: The next / successor task. + :type related_process: ifcopenshell.entity_instance.entity_instance + :return: None + :rtype: None + + Example:: + + # Let's imagine we are creating a construction schedule. All tasks + # need to be part of a work schedule. + schedule = ifcopenshell.api.run("sequence.add_work_schedule", model, name="Construction Schedule A") + + # Let's imagine a root construction task + construction = ifcopenshell.api.run("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.run("sequence.add_task", model, + parent_task=construction, name="Zone 1", identification="C.1") + zone2 = ifcopenshell.api.run("sequence.add_task", model, + parent_task=construction, name="Zone 2", identification="C.2") + + # Zone 1 finishes, then zone 2 starts. + ifcopenshell.api.run("sequence.assign_sequence", model, relating_process=zone1, related_process=zone2) + + # Let's make them unrelated + ifcopenshell.api.run("sequence.unassign_sequence", model, + relating_process=zone1, related_process=zone2) + """ self.file = file self.settings = { - "relating_process": None, - "related_process": None, + "relating_process": relating_process, + "related_process": related_process, } - for key, value in settings.items(): - self.settings[key] = value def execute(self): for rel in self.settings["related_process"].IsSuccessorFrom or []: