ifcos-impl-sequence
IFC Scheduling and 4D BIM Implementation
Quick Reference
Critical Warnings
- ALWAYS use
ifcopenshell.api.run("sequence.*", ...)for schedule mutations. NEVER createIfcTask,IfcWorkSchedule, orIfcRelSequencedirectly withmodel.create_entity(). - ALWAYS call
add_task_timeBEFOREedit_task_time. TheIfcTaskTimeentity MUST exist before editing. - ALWAYS use ISO 8601 strings for dates (
"2026-04-01") and durations ("P5D") in IFC4+. NEVER pass Pythondatetimeobjects directly toedit_task_time. - NEVER assign
IfcTaskTimeto parent/summary tasks. ONLY leaf tasks (no subtasks) receive time data. - NEVER pass both
work_scheduleandparent_tasktoadd_task. These are mutually exclusive. - NEVER create cyclical sequence relationships.
cascade_schedulewill recurse infinitely. - ALWAYS call
cascade_scheduleafter modifying task durations or sequences. Dates do NOT propagate automatically. - ALWAYS pass
productsas 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
# IFC4 — Complete construction schedule from scratch
import ifcopenshell
import ifcopenshell.api
# Step 1: Create IFC file with project (REQUIRED before any scheduling)
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)
# Step 2: Create work schedule (directly, or within a work plan)
schedule = ifcopenshell.api.run("sequence.add_work_schedule", model,
name="Construction Schedule",
predefined_type="PLANNED")
# Step 3: Create tasks
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")
# Step 4: Add time data to tasks
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"})
# Step 5: Create dependency
ifcopenshell.api.run("sequence.assign_sequence", model,
relating_process=task_a,
related_process=task_b,
sequence_type="FINISH_START")
# Step 6: Cascade dates (task_b start computed from task_a finish)
ifcopenshell.api.run("sequence.cascade_schedule", model, task=task_a)
model.write("schedule.ifc")
Pattern 2: Hierarchical Work Breakdown Structure
# IFC4 — WBS with parent/child task nesting
schedule = ifcopenshell.api.run("sequence.add_work_schedule", model,
name="Phase 1", predefined_type="PLANNED")
# Root task (summary — NO time data)
phase_struct = ifcopenshell.api.run("sequence.add_task", model,
work_schedule=schedule,
name="Structural Works",
identification="1")
# Subtasks (leaf tasks — GET time data)
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")
# Add time data ONLY to leaf tasks
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})
# Sequence the leaf tasks
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
# IFC4 — Standard 5-day work week with holidays
calendar = ifcopenshell.api.run("sequence.add_work_calendar", model,
name="Standard 5-Day Week")
# Define working times (Monday-Friday)
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]}) # Mon=1..Fri=5
# Add working hours
ifcopenshell.api.run("sequence.add_time_period", model,
recurrence_pattern=pattern,
start_time="08:00:00",
end_time="17:00:00")
# Add exception (holiday)
exception = ifcopenshell.api.run("sequence.add_work_time", model,
work_calendar=calendar,
time_type="ExceptionTimes")
Pattern 4: 4D BIM — Linking Tasks to Products
# IFC4 — Connect tasks to building elements for 4D visualization
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")
# Link products as outputs of tasks (IfcRelAssignsToProcess)
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)
# Query: which products does a task build?
# Use ifcopenshell.util.sequence.get_task_outputs(task)
Pattern 5: Gantt Chart Data Extraction
# IFC4 — Extract scheduling data for Gantt chart rendering
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['id']}: {row['name']} "
f"({row['start']} → {row['finish']}, {row['duration']})")
# Get predecessors for dependency arrows
for rel in task.IsSuccessorFrom or []:
pred = rel.RelatingProcess
print(f"{prefix} ← depends on: {pred.Identification} "
f"({rel.SequenceType})")
# Recurse into subtasks
for subtask in ifcopenshell.util.sequence.get_nested_tasks(task):
extract_task_data(subtask, indent + 1)
Pattern 6: Critical Path Analysis
# IFC4 — Calculate critical path and floats
ifcopenshell.api.run("sequence.recalculate_schedule", model,
work_schedule=schedule)
# After recalculation, check task floats
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
# IFC4 — Add 2-day lag after concrete pour (curing time)
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
# IFC4 — Update task attributes
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)
# IFC4 — Removes task, all subtasks, and all relationships
ifcopenshell.api.run("sequence.remove_task", model, task=task)
Work Plan with Multiple Schedules
# IFC4 — Group schedules under a work plan
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
# Navigate task hierarchy
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))
# Find task context
parent = seq_util.get_parent_task(task)
owning_schedule = seq_util.get_task_work_schedule(task)
# 4D BIM queries
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)
# Calendar queries
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)
# Schedule overview
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 | Remaining 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
- API Method Signatures — Complete signatures for all 40 sequence functions
- Working Code Examples — End-to-end scheduling scenarios
- Anti-Patterns — Common scheduling mistakes and corrections
More from openaec-foundation/computational-design-day-delft-march-2026
blender-core-api
Guides Blender Python API usage including bpy module structure, RNA data access, context system, dependency graph, and operator invocation. Activates when writing bpy scripts, creating Blender addons, or accessing Blender data blocks programmatically.
1blender-syntax-panels
Defines Blender UI panel creation including bpy.types.Panel, draw() method, UILayout API (row/column/box/split), bl_space_type, bl_region_type, bl_category, sub-panels, draw_header, menus, and UIList. Activates when creating custom Blender panels, building addon interfaces, or working with UILayout elements.
1ifcos-syntax-api
Documents the ifcopenshell.api module system with all 30+ API modules, invocation patterns via api.run() and direct module calls, parameter conventions, and module categorization. Activates when creating IFC entities, modifying properties, managing spatial structure, or using any ifcopenshell.api function.
1bonsai-impl-bcf
Guides implementation of BIM Collaboration Format (BCF) workflows in Bonsai including creating BCF topics, adding viewpoints with camera snapshots, managing comments, importing/exporting BCF files (v2.1 and v3.0), and integrating BCF issue tracking with IFC element references. Activates when working with BCF files, BIM issue tracking, clash report management, or collaboration workflows in Bonsai.
1ifcos-impl-mep
Guides MEP (Mechanical, Electrical, Plumbing) modeling in IFC using ifcopenshell.api.system including IfcSystem, IfcDistributionElement, ports, connections, flow segments, fittings, and MEP-specific property sets. Activates when creating HVAC systems, piping networks, electrical circuits, or MEP elements in IFC models.
1ifcos-impl-cost
Guides IFC cost management using ifcopenshell.api.cost including cost schedules, cost items, cost values, cost quantities, and 5D BIM workflows. Activates when implementing cost estimation in IFC models, creating cost schedules, or linking quantities to cost items.
1