| name | ifcos-impl-sequence |
| description | Use when implementing construction schedules or 4D BIM timelines in IFC -- work schedules, tasks, task dependencies, and Gantt chart data extraction. Prevents the common mistake of not linking tasks to elements (no 4D visualization possible). Covers ifcopenshell.api.sequence, work schedules, task time relationships, and construction sequence modeling. Keywords: schedule, 4D BIM, work schedule, task, Gantt, timeline, construction sequence, IfcWorkSchedule, IfcTask, task dependency, construction planning, project timeline, phase planning.
|
| license | MIT |
| compatibility | Designed for Claude Code. Requires IfcOpenShell Python library. |
| metadata | {"author":"OpenAEC-Foundation","version":"1.0"} |
IFC Scheduling and 4D BIM Implementation
Quick Reference
Critical Warnings
- ALWAYS use
ifcopenshell.api.run("sequence.*", ...) for schedule mutations. NEVER create IfcTask, IfcWorkSchedule, or IfcRelSequence directly with model.create_entity().
- ALWAYS call
add_task_time BEFORE edit_task_time. The IfcTaskTime entity MUST exist before editing.
- ALWAYS use ISO 8601 strings for dates (
"2026-04-01") and durations ("P5D") in IFC4+. NEVER pass Python datetime objects directly to edit_task_time.
- NEVER assign
IfcTaskTime to parent/summary tasks. ONLY leaf tasks (no subtasks) receive time data.
- NEVER pass both
work_schedule and parent_task to add_task. These are mutually exclusive.
- NEVER create cyclical sequence relationships.
cascade_schedule will recurse infinitely.
- ALWAYS call
cascade_schedule after modifying task durations or sequences. Dates do NOT propagate automatically.
- ALWAYS pass
products as a list in v0.8+ relationship functions (e.g., assign_process).
- NEVER forget to create a project bootstrap (IfcProject, units, contexts) before creating schedules.
Version Differences: IFC2X3 vs IFC4+
| Feature | IFC2X3 | IFC4 / IFC4X3 |
|---|
| Date format | IfcDateAndTime entity | ISO 8601 string ("2026-04-01") |
| Duration format | IfcDateAndTime entity | ISO 8601 duration ("P5D", "P2W") |
add_date_time returns | IfcDateAndTime entity | Formatted string |
| Task predefined types | Limited | CONSTRUCTION, DEMOLITION, MAINTENANCE, MOVE, OPERATION, USERDEFINED, NOTDEFINED |
| Schedule predefined types | Limited | ACTUAL, BASELINE, PLANNED, USERDEFINED, NOTDEFINED |
IfcWorkPlan | Not available | Available — groups related schedules |
| Lag time support | Basic | Full ISO 8601 duration with duration types |
| Recurrence patterns | Limited | Full IfcRecurrencePattern support |
ALWAYS check model.schema before writing scheduling code. Use the IFC4+ patterns unless targeting legacy IFC2X3 files.
Decision Tree: What Do You Need?
What scheduling operation do you need?
├── Create a construction schedule from scratch?
│ └── Follow: Full Schedule Bootstrap (Pattern 1)
│
├── Add tasks to an existing schedule?
│ ├── Top-level task?
│ │ └── add_task(work_schedule=schedule, ...)
│ └── Subtask of existing task?
│ └── add_task(parent_task=parent, ...)
│
├── Set task dates and durations?
│ ├── Task has no IfcTaskTime yet?
│ │ └── add_task_time(task=task) → then edit_task_time(...)
│ └── Task already has IfcTaskTime?
│ └── edit_task_time(task_time=task.TaskTime, ...)
│
├── Define task dependencies?
│ ├── Simple finish-to-start?
│ │ └── assign_sequence(sequence_type="FINISH_START")
│ ├── With lag/delay between tasks?
│ │ └── assign_sequence → assign_lag_time
│ └── Other dependency types?
│ └── assign_sequence with FINISH_FINISH, START_START, or START_FINISH
│
├── Create a work calendar (working days/hours)?
│ └── Follow: Calendar Setup (Pattern 3)
│
├── Link tasks to building products (4D BIM)?
│ ├── Product is INPUT to task (consumed/used)?
│ │ └── assign_process(relating_process=task, related_object=product)
│ └── Product is OUTPUT of task (created)?
│ └── assign_product(relating_product=product, related_object=task)
│
├── Propagate dates through task network?
│ └── cascade_schedule(task=starting_task)
│
├── Calculate critical path and floats?
│ └── recalculate_schedule(work_schedule=schedule)
│
├── Extract Gantt chart data?
│ └── Follow: Gantt Data Extraction (Pattern 5)
│
└── Compare schedule baselines?
└── create_baseline → compare schedules
Essential Patterns
Pattern 1: Full Schedule Bootstrap
import ifcopenshell
import ifcopenshell.api
model = ifcopenshell.api.run("project.create_file", version="IFC4")
project = ifcopenshell.api.run("root.create_entity", model,
ifc_class="IfcProject", name="My Project")
ifcopenshell.api.run("unit.assign_unit", model)
schedule = ifcopenshell.api.run("sequence.add_work_schedule", model,
name="Construction Schedule",
predefined_type="PLANNED")
task_a = ifcopenshell.api.run("sequence.add_task", model,
work_schedule=schedule,
name="Foundations",
identification="A")
task_b = ifcopenshell.api.run("sequence.add_task", model,
work_schedule=schedule,
name="Superstructure",
identification="B")
time_a = ifcopenshell.api.run("sequence.add_task_time", model, task=task_a)
ifcopenshell.api.run("sequence.edit_task_time", model,
task_time=time_a,
attributes={
"ScheduleStart": "2026-04-01",
"ScheduleDuration": "P10D",
})
time_b = ifcopenshell.api.run("sequence.add_task_time", model, task=task_b)
ifcopenshell.api.run("sequence.edit_task_time", model,
task_time=time_b,
attributes={"ScheduleDuration": "P20D"})
ifcopenshell.api.run("sequence.assign_sequence", model,
relating_process=task_a,
related_process=task_b,
sequence_type="FINISH_START")
ifcopenshell.api.run(, model, task=task_a)
model.write()
Pattern 2: Hierarchical Work Breakdown Structure
schedule = ifcopenshell.api.run("sequence.add_work_schedule", model,
name="Phase 1", predefined_type="PLANNED")
phase_struct = ifcopenshell.api.run("sequence.add_task", model,
work_schedule=schedule,
name="Structural Works",
identification="1")
formwork = ifcopenshell.api.run("sequence.add_task", model,
parent_task=phase_struct,
name="Formwork",
identification="1.1")
rebar = ifcopenshell.api.run("sequence.add_task", model,
parent_task=phase_struct,
name="Reinforcement",
identification="1.2")
pour = ifcopenshell.api.run("sequence.add_task", model,
parent_task=phase_struct,
name="Concrete Pour",
identification="1.3")
for task, dur in [(formwork, "P5D"), (rebar, "P3D"), (pour, "P1D")]:
tt = ifcopenshell.api.run("sequence.add_task_time", model, task=task)
ifcopenshell.api.run("sequence.edit_task_time", model,
task_time=tt,
attributes={"ScheduleDuration": dur})
ifcopenshell.api.run("sequence.assign_sequence", model,
relating_process=formwork, related_process=rebar)
ifcopenshell.api.run("sequence.assign_sequence", model,
relating_process=rebar, related_process=pour)
Pattern 3: Work Calendar Setup
calendar = ifcopenshell.api.run("sequence.add_work_calendar", model,
name="Standard 5-Day Week")
work_time = ifcopenshell.api.run("sequence.add_work_time", model,
work_calendar=calendar,
time_type="WorkingTimes")
pattern = ifcopenshell.api.run("sequence.assign_recurrence_pattern", model,
parent=work_time,
recurrence_type="WEEKLY")
ifcopenshell.api.run("sequence.edit_recurrence_pattern", model,
recurrence_pattern=pattern,
attributes={"WeekdayComponent": [1, 2, 3, 4, 5]})
ifcopenshell.api.run("sequence.add_time_period", model,
recurrence_pattern=pattern,
start_time="08:00:00",
end_time="17:00:00")
exception = ifcopenshell.api.run("sequence.add_work_time", model,
work_calendar=calendar,
time_type="ExceptionTimes")
Pattern 4: 4D BIM: Linking Tasks to Products
wall = ifcopenshell.api.run("root.create_entity", model,
ifc_class="IfcWall", name="Exterior Wall A")
slab = ifcopenshell.api.run("root.create_entity", model,
ifc_class="IfcSlab", name="Ground Floor Slab")
ifcopenshell.api.run("sequence.assign_process", model,
relating_process=pour,
related_object=slab)
ifcopenshell.api.run("sequence.assign_process", model,
relating_process=formwork,
related_object=wall)
Pattern 5: Gantt Chart Data Extraction
import ifcopenshell
import ifcopenshell.util.sequence
model = ifcopenshell.open("schedule.ifc")
schedules = model.by_type("IfcWorkSchedule")
for schedule in schedules:
print(f"Schedule: {schedule.Name}")
for task in ifcopenshell.util.sequence.get_root_tasks(schedule):
extract_task_data(task, indent=0)
def extract_task_data(task, indent=0):
"""Recursively extract task data for Gantt rendering."""
prefix = " " * indent
task_time = task.TaskTime
row = {
"id": task.Identification,
"name": task.Name,
"start": task_time.ScheduleStart if task_time else None,
"finish": task_time.ScheduleFinish if task_time else None,
"duration": task_time.ScheduleDuration if task_time else None,
"completion": task_time.Completion if task_time else None,
"is_critical": task_time.IsCritical if task_time else None,
}
print(f"{prefix}{row[]}: "
)
rel task.IsSuccessorFrom []:
pred = rel.RelatingProcess
(
)
subtask ifcopenshell.util.sequence.get_nested_tasks(task):
extract_task_data(subtask, indent + )
Pattern 6: Critical Path Analysis
ifcopenshell.api.run("sequence.recalculate_schedule", model,
work_schedule=schedule)
for task in model.by_type("IfcTask"):
tt = task.TaskTime
if tt:
is_critical = (tt.TotalFloat == "P0D") if tt.TotalFloat else False
print(f"{task.Name}: TotalFloat={tt.TotalFloat}, "
f"FreeFloat={tt.FreeFloat}, Critical={is_critical}")
Common Operations
Add Lag Time Between Tasks
seq = ifcopenshell.api.run("sequence.assign_sequence", model,
relating_process=pour_task,
related_process=next_task,
sequence_type="FINISH_START")
ifcopenshell.api.run("sequence.assign_lag_time", model,
rel_sequence=seq,
lag_value="P2D",
duration_type="WORKTIME")
Modify Existing Task
ifcopenshell.api.run("sequence.edit_task", model,
task=task,
attributes={
"Name": "Updated Task Name",
"Identification": "B.1",
"Description": "Revised scope",
"PredefinedType": "CONSTRUCTION",
})
Remove Task (Cascading Delete)
ifcopenshell.api.run("sequence.remove_task", model, task=task)
Work Plan with Multiple Schedules
plan = ifcopenshell.api.run("sequence.add_work_plan", model,
name="Master Construction Plan")
planned = ifcopenshell.api.run("sequence.add_work_schedule", model,
name="Planned Schedule",
predefined_type="PLANNED",
work_plan=plan)
actual = ifcopenshell.api.run("sequence.add_work_schedule", model,
name="Actual Schedule",
predefined_type="ACTUAL",
work_plan=plan)
Query Schedule Data with Utilities
import ifcopenshell.util.sequence as seq_util
root_tasks = seq_util.get_root_tasks(schedule)
children = seq_util.get_nested_tasks(parent_task)
all_tasks = list(seq_util.get_all_nested_tasks(parent_task))
parent = seq_util.get_parent_task(task)
owning_schedule = seq_util.get_task_work_schedule(task)
products = seq_util.get_task_outputs(task)
inputs = seq_util.get_task_inputs(task)
resources = seq_util.get_task_resources(task)
tasks_for_wall = seq_util.get_tasks_for_product(wall, schedule)
is_work = seq_util.is_working_day(date, calendar)
work_days = seq_util.count_working_days(start, finish, calendar)
new_date = seq_util.offset_date(start, duration, "WORKTIME", calendar)
start, end = seq_util.guess_date_range(schedule)
IfcTaskTime Attribute Reference
| Attribute | Type (IFC4+) | Description |
|---|
ScheduleStart | ISO date string | Planned start date |
ScheduleFinish | ISO date string | Planned finish date (auto-calculated if duration set) |
ScheduleDuration | ISO duration | Planned duration ("P5D", "P2W", "P1M") |
ActualStart | ISO date string | Actual start date |
ActualFinish | ISO date string | Actual finish date |
ActualDuration | ISO duration | Actual duration |
EarlyStart | ISO date string | CPM forward pass early start |
EarlyFinish | ISO date string | CPM forward pass early finish |
LateStart | ISO date string | CPM backward pass late start |
LateFinish | ISO date string | CPM backward pass late finish |
FreeFloat | ISO duration | Free float (populated by recalculate_schedule) |
TotalFloat | ISO duration | Total float (populated by recalculate_schedule) |
IsCritical | bool | On critical path (populated by recalculate_schedule) |
Completion | float | Percentage complete (0.0 to 1.0) |
DurationType | enum | ELAPSEDDAYS, WORKTIME, CALENDARTIME |
StatusTime | ISO date string | Date of last status update |
RemainingTime | ISO duration |
Sequence Types Reference
| Type | Constant | Description | Construction Example |
|---|
| Finish-to-Start | FINISH_START | Predecessor MUST finish before successor starts | Pour concrete → cure → strip formwork |
| Finish-to-Finish | FINISH_FINISH | Predecessor finish constrains successor finish | Painting must finish when inspection finishes |
| Start-to-Start | START_START | Predecessor start constrains successor start | Excavation starts → hauling starts simultaneously |
| Start-to-Finish | START_FINISH | Predecessor start constrains successor finish | Rare — used for just-in-time delivery |
ALWAYS default to FINISH_START unless there is a specific reason for another type. It represents 90%+ of construction dependencies.
Dependencies
- ifcos-syntax-api — API invocation patterns (
api.run() vs direct calls), module table, parameter conventions
- ifcos-syntax-fileio — File I/O (
ifcopenshell.open(), model.write(), transactions)
Reference Links