| name | 4d-simulation |
| description | Create 4D construction simulations by linking BIM elements to project schedules. Generate time-based visualizations, sequence analysis, and construction phasing with Gantt integration. |
| homepage | https://datadrivenconstruction.io |
| metadata | {"openclaw":{"emoji":"🎬","os":["darwin","linux","win32"],"homepage":"https://datadrivenconstruction.io","requires":{"bins":"[Truncated]"}}} |
4D Simulation for Construction
Overview
Based on DDC methodology (Chapter 3.3), this skill implements 4D BIM simulations - linking 3D model elements to the 4th dimension: time. Visualize construction sequences, detect scheduling conflicts, and optimize work phasing.
Book Reference: "4D, 6D-8D и расчет CO2" / "4D-8D BIM and CO2 Calculation"
"4D моделирование позволяет визуализировать последовательность строительства и выявлять конфликты на этапе планирования."
— DDC Book, Chapter 3.3
Quick Start
import pandas as pd
from datetime import datetime, timedelta
elements = pd.DataFrame({
'ElementId': ['E001', 'E002', 'E003', 'E004'],
'Category': ['Foundation', 'Column', 'Beam', 'Slab'],
'Level': ['Level 0', 'Level 1', 'Level 1', 'Level 1'],
'Start_Date': ['2024-01-01', '2024-01-15', '2024-02-01', '2024-02-15'],
'End_Date': ['2024-01-14', '2024-01-31', '2024-02-14', '2024-02-28'],
'Phase': ['Structure', 'Structure', 'Structure', 'Structure']
})
elements['Start_Date'] = pd.to_datetime(elements['Start_Date'])
elements['End_Date'] = pd.to_datetime(elements['End_Date'])
elements['Duration_Days'] = (elements['End_Date'] - elements['Start_Date']).dt.days
target_date = pd.to_datetime('2024-01-20')
active_elements = elements[
(elements['Start_Date'] <= target_date) &
(elements['End_Date'] >= target_date)
]
print(f"Elements under construction on {target_date.date()}:")
print(active_elements[['ElementId', 'Category']])
4D Data Model
Schedule-Element Linking
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
from typing import List, Dict, Optional
class ScheduleElementLinker:
"""Link BIM elements to schedule activities"""
def __init__(self, elements_df: pd.DataFrame, schedule_df: pd.DataFrame):
self.elements = elements_df.copy()
self.schedule = schedule_df.copy()
self.links = pd.DataFrame()
def auto_link_by_category(self, mapping: Dict[str, str]):
"""Auto-link elements to activities by category mapping
Args:
mapping: Dict mapping element categories to activity names
e.g., {'Wall': 'Structural Walls', 'Slab': 'Floor Construction'}
"""
links = []
for category, activity_name in mapping.items():
category_elements = self.elements[
self.elements['Category'] == category
]['ElementId'].tolist()
activity = self.schedule[
self.schedule['Activity'].str.contains(activity_name, case=False)
]
activity.empty category_elements:
elem_id category_elements:
links.append({
: elem_id,
: activity.iloc[][],
: activity.iloc[][],
: activity.iloc[][],
: activity.iloc[][]
})
.links = pd.DataFrame(links)
.links
():
levels = (.elements[].unique())
links = []
i, level (levels):
level_elements = .elements[.elements[] == level]
level_activity = .schedule[
.schedule[]..contains(level, =)
]
level_activity.empty:
_, elem level_elements.iterrows():
links.append({
: elem[],
: level_activity.iloc[][],
: level_activity.iloc[][],
: level_activity.iloc[][],
: level_activity.iloc[][]
})
.links = pd.DataFrame(links)
.links
():
element = .elements[.elements[] == element_id]
activity = .schedule[.schedule[] == activity_id]
element.empty activity.empty:
ValueError()
new_link = pd.DataFrame([{
: element_id,
: activity_id,
: activity.iloc[][],
: activity.iloc[][],
: activity.iloc[][]
}])
.links = pd.concat([.links, new_link], ignore_index=)
.links
():
.elements.merge(
.links[[, , , ]],
on=,
how=
)
4D Simulation Engine
class Simulation4D:
"""4D construction simulation engine"""
def __init__(self, linked_elements: pd.DataFrame):
self.elements = linked_elements.copy()
self.elements['Start_Date'] = pd.to_datetime(self.elements['Start_Date'])
self.elements['End_Date'] = pd.to_datetime(self.elements['End_Date'])
self.project_start = self.elements['Start_Date'].min()
self.project_end = self.elements['End_Date'].max()
def get_state_at_date(self, target_date: datetime) -> pd.DataFrame:
"""Get element states at a specific date"""
target = pd.to_datetime(target_date)
conditions = [
target < self.elements['Start_Date'],
(self.elements['Start_Date'] <= target) & (target <= self.elements['End_Date']),
target > self.elements['End_Date']
]
choices = ['not_started', 'in_progress', 'completed']
.elements[] = np.select(conditions, choices, default=)
.elements.copy()
() -> []:
timeline = []
current_date = .project_start
current_date <= .project_end:
state = .get_state_at_date(current_date)
snapshot = {
: current_date,
: (state[state[] == ]),
: (state[state[] == ]),
: (state[state[] == ]),
: (state)
}
snapshot[] = (snapshot[] / snapshot[]) *
timeline.append(snapshot)
current_date += timedelta(days=interval_days)
timeline
() -> pd.DataFrame:
state = .get_state_at_date(target_date)
state[state[] == ]
() -> pd.DataFrame:
sequence = .elements.groupby([, ]).agg({
: ,
: ,
:
}).rename(columns={: })
sequence[] = (sequence[] - sequence[]).dt.days
sequence = sequence.sort_values().reset_index()
sequence
() -> pd.DataFrame:
dates = pd.date_range(.project_start, .project_end, freq=)
parallel_work = []
date dates:
state = .get_state_at_date(date)
in_progress = state[state[] == ]
(in_progress) > :
categories = in_progress[].unique().tolist()
levels = in_progress[].unique().tolist()
parallel_work.append({
: date,
: (in_progress),
: .join(categories),
: .join(levels)
})
pd.DataFrame(parallel_work)
Gantt Chart Integration
Gantt Chart Generator
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
from matplotlib.patches import Patch
class GanttChartGenerator:
"""Generate Gantt charts for 4D simulation"""
def __init__(self, elements: pd.DataFrame):
self.elements = elements.copy()
self.colors = {
'Foundation': '#8B4513',
'Column': '#4169E1',
'Beam': '#228B22',
'Slab': '#DC143C',
'Wall': '#FF8C00',
'Roof': '#9932CC',
'MEP': '#20B2AA',
'Finishes': '#FFD700'
}
def create_gantt(self, group_by='Category', figsize=(14, 8)):
"""Create Gantt chart grouped by specified column"""
fig, ax = plt.subplots(figsize=figsize)
groups = self.elements.groupby(group_by)
y_pos = 0
y_labels = []
legend_elements = []
for group_name, group_df in groups:
color = self.colors.get(group_name, '#808080')
_, row group_df.iterrows():
start = row[]
duration = (row[] - row[]).days
ax.barh(y_pos, duration, left=start, height=,
color=color, alpha=, edgecolor=, linewidth=)
y_pos +=
y_labels.append(group_name)
legend_elements.append(Patch(facecolor=color, label=group_name))
ax.set_yticks(((.elements)))
ax.set_yticklabels(.elements[])
ax.set_xlabel()
ax.set_title(, fontsize=, fontweight=)
ax.xaxis.set_major_formatter(mdates.DateFormatter())
ax.xaxis.set_major_locator(mdates.WeekdayLocator(interval=))
plt.xticks(rotation=)
ax.legend(handles=legend_elements, loc=)
ax.grid(axis=, alpha=)
plt.tight_layout()
fig
():
fig, ax = plt.subplots(figsize=figsize)
levels = (.elements[].unique())
i, level (levels):
level_elements = .elements[.elements[] == level]
_, row level_elements.iterrows():
color = .colors.get(row[], )
start = row[]
duration = (row[] - row[]).days
ax.barh(i, duration, left=start, height=,
color=color, alpha=, edgecolor=, linewidth=)
ax.set_yticks(((levels)))
ax.set_yticklabels(levels)
ax.set_xlabel()
ax.set_ylabel()
ax.set_title(, fontsize=, fontweight=)
ax.xaxis.set_major_formatter(mdates.DateFormatter())
plt.xticks(rotation=)
legend_patches = [Patch(color=c, label=cat) cat, c .colors.items()
cat .elements[].values]
ax.legend(handles=legend_patches, loc=)
plt.tight_layout()
fig
():
df = pd.DataFrame(timeline)
fig, ax = plt.subplots(figsize=figsize)
ax.plot(df[], df[], , linewidth=, label=)
ax.fill_between(df[], , df[], alpha=)
milestone [, , , ]:
ax.axhline(y=milestone, color=, linestyle=, alpha=)
ax.text(df[].iloc[], milestone + , ,
fontsize=, color=)
ax.set_xlabel()
ax.set_ylabel()
ax.set_title(, fontsize=, fontweight=)
ax.set_ylim(, )
ax.xaxis.set_major_formatter(mdates.DateFormatter())
plt.xticks(rotation=)
plt.tight_layout()
fig
Sequence Analysis
Construction Sequence Optimizer
class SequenceAnalyzer:
"""Analyze and optimize construction sequences"""
def __init__(self, elements: pd.DataFrame):
self.elements = elements.copy()
self.dependencies = []
def add_dependency(self, predecessor: str, successor: str, lag_days: int = 0):
"""Add dependency between elements"""
self.dependencies.append({
'predecessor': predecessor,
'successor': successor,
'lag_days': lag_days
})
def check_sequence_violations(self) -> List[Dict]:
"""Check for sequence violations"""
violations = []
for dep in self.dependencies:
pred = self.elements[self.elements['ElementId'] == dep['predecessor']]
succ = self.elements[self.elements['ElementId'] == dep['successor']]
if pred.empty or succ.empty:
continue
pred_end = pred.iloc[0]['End_Date']
succ_start = succ.iloc[0]['Start_Date']
required_start = pred_end + timedelta(days=dep[])
succ_start < required_start:
violations.append({
: dep[],
: dep[],
: pred_end,
: succ_start,
: required_start,
: (required_start - succ_start).days
})
violations
() -> []:
conflicts = []
location_col =
location, group .elements.groupby(location_col):
(group) < :
i, row1 group.iterrows():
j, row2 group.iterrows():
i >= j:
overlap = (row1[] <= row2[]
row2[] <= row1[])
overlap:
conflicts.append({
: location,
: row1[],
: row1[],
: row2[],
: row2[],
: (row1[], row2[]),
: (row1[], row2[])
})
conflicts
() -> []:
graph = {}
elem .elements[]:
graph[elem] = {
: [],
:
}
dep .dependencies:
dep[] graph:
graph[dep[]][].append(dep[])
_, row .elements.iterrows():
row[] graph:
graph[row[]][] = (row[] - row[]).days
():
node memo:
memo[node]
graph[node][]:
graph[node][]
max_pred = (
longest_path(pred, memo) pred graph[node][]
)
memo[node] = max_pred + graph[node][]
memo[node]
path_lengths = {elem: longest_path(elem) elem graph.keys()}
max_length = (path_lengths.values())
critical = [elem elem, length path_lengths.items() length == max_length]
critical
Export and Integration
def export_4d_schedule(elements: pd.DataFrame, output_path: str):
"""Export 4D schedule to Excel with multiple views"""
with pd.ExcelWriter(output_path, engine='openpyxl') as writer:
elements.to_excel(writer, sheet_name='Schedule', index=False)
level_summary = elements.groupby('Level').agg({
'ElementId': 'count',
'Start_Date': 'min',
'End_Date': 'max'
}).rename(columns={'ElementId': 'Element_Count'})
level_summary['Duration_Days'] = (level_summary['End_Date'] - level_summary['Start_Date']).dt.days
level_summary.to_excel(writer, sheet_name='By_Level')
cat_summary = elements.groupby('Category').agg({
'ElementId': 'count',
'Start_Date': 'min',
'End_Date': 'max'
}).rename(columns={'ElementId': 'Element_Count'})
cat_summary.to_excel(writer, sheet_name='By_Category')
return output_path
Quick Reference
| Concept | Description |
|---|
| 4D = 3D + Time | BIM model linked to schedule |
| Activity | Scheduled work item |
| Element State | not_started / in_progress / completed |
| Critical Path | Longest sequence determining project duration |
Resources
Next Steps
- See
gantt-chart for schedule visualization
- See
co2-estimation for 6D (sustainability) analysis
- See
clash-detection-analysis for 4D conflict detection