ifcos-impl-sequence

Installation
SKILL.md

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

# 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

Related skills

More from openaec-foundation/computational-design-day-delft-march-2026

Installs
1
GitHub Stars
1
First Seen
Mar 24, 2026