| name | clash-detection-analysis |
| description | Detect and analyze geometric clashes between BIM elements. Identify hard clashes, soft clashes, and workflow conflicts using spatial analysis and rule-based detection. |
| homepage | https://datadrivenconstruction.io |
| metadata | {"openclaw":{"emoji":"🚀","os":["darwin","linux","win32"],"homepage":"https://datadrivenconstruction.io","requires":{"bins":"[Truncated]","anyBins":"[Truncated]"}}} |
Clash Detection Analysis
Overview
This skill implements automated clash detection for BIM models. Identify conflicts between building elements before construction to prevent costly rework and delays.
Types of Clashes:
- Hard Clash: Physical intersection of elements
- Soft Clash: Clearance/tolerance violations
- Workflow Clash: Scheduling/sequencing conflicts
"Обнаружение коллизий на этапе проектирования может сократить затраты на исправление ошибок до 10 раз по сравнению с исправлением на стройплощадке."
Quick Start
import ifcopenshell
import ifcopenshell.geom
import numpy as np
from itertools import combinations
ifc = ifcopenshell.open("model.ifc")
structural = ifc.by_type("IfcColumn") + ifc.by_type("IfcBeam")
mep = ifc.by_type("IfcPipeSegment") + ifc.by_type("IfcDuctSegment")
settings = ifcopenshell.geom.settings()
def get_bbox(element):
try:
shape = ifcopenshell.geom.create_shape(settings, element)
verts = np.array(shape.geometry.verts).reshape(-1, 3)
return verts.min(axis=0), verts.max(axis=0)
except:
return None, None
def check_bbox_clash(bbox1, bbox2):
min1, max1 = bbox1
min2, max2 = bbox2
if min1 is None or min2 is None:
return False
return np.all(max1 >= min2) and np.all(max2 >= min1)
clashes = []
for s_elem in structural:
for m_elem in mep:
bbox1 = get_bbox(s_elem)
bbox2 = get_bbox(m_elem)
if check_bbox_clash(bbox1, bbox2):
clashes.append({
'element1': s_elem.GlobalId,
'element2': m_elem.GlobalId,
'type': 'Structure-MEP'
})
print(f"Found {len(clashes)} potential clashes")
Clash Detection Engine
Core Detector Class
import ifcopenshell
import ifcopenshell.geom
import numpy as np
import pandas as pd
from dataclasses import dataclass
from typing import List, Dict, Optional, Tuple
from itertools import combinations
from scipy.spatial import cKDTree
@dataclass
class Clash:
element1_id: str
element1_type: str
element1_name: str
element2_id: str
element2_type: str
element2_name: str
clash_type: str
distance: float
location: Tuple[float, float, float]
severity: str
class ClashDetector:
"""Detect clashes between BIM elements"""
def __init__(self, ifc_path: str):
self.model = ifcopenshell.open(ifc_path)
self.settings = ifcopenshell.geom.settings()
self.settings.set(self.settings.USE_WORLD_COORDS, True)
self._geometry_cache = {}
.clashes: [Clash] = []
():
element.GlobalId ._geometry_cache:
._geometry_cache[element.GlobalId]
:
shape = ifcopenshell.geom.create_shape(.settings, element)
verts = np.array(shape.geometry.verts).reshape(-, )
faces = np.array(shape.geometry.faces).reshape(-, )
geom = {
: verts,
: faces,
: verts.(axis=),
: verts.(axis=),
: verts.mean(axis=)
}
._geometry_cache[element.GlobalId] = geom
geom
:
() -> [Clash]:
group1 = []
ifc_type group1_types:
group1.extend(.model.by_type(ifc_type))
group2 = []
ifc_type group2_types:
group2.extend(.model.by_type(ifc_type))
clashes = []
elem1 group1:
geom1 = ._get_geometry(elem1)
geom1 :
elem2 group2:
elem1.GlobalId == elem2.GlobalId:
geom2 = ._get_geometry(elem2)
geom2 :
._bbox_intersect(geom1, geom2):
intersection = ._check_intersection(geom1, geom2)
intersection[]:
clash = Clash(
element1_id=elem1.GlobalId,
element1_type=elem1.is_a(),
element1_name=elem1.Name ,
element2_id=elem2.GlobalId,
element2_type=elem2.is_a(),
element2_name=elem2.Name ,
clash_type=,
distance=intersection[],
location=(intersection[]),
severity=._classify_severity(intersection[])
)
clashes.append(clash)
.clashes.extend(clashes)
clashes
() -> [Clash]:
group1 = []
ifc_type group1_types:
group1.extend(.model.by_type(ifc_type))
group2 = []
ifc_type group2_types:
group2.extend(.model.by_type(ifc_type))
clashes = []
elem1 group1:
geom1 = ._get_geometry(elem1)
geom1 :
elem2 group2:
elem1.GlobalId == elem2.GlobalId:
geom2 = ._get_geometry(elem2)
geom2 :
distance = ._min_distance(geom1, geom2)
distance < clearance distance > :
clash = Clash(
element1_id=elem1.GlobalId,
element1_type=elem1.is_a(),
element1_name=elem1.Name ,
element2_id=elem2.GlobalId,
element2_type=elem2.is_a(),
element2_name=elem2.Name ,
clash_type=,
distance=distance,
location=((geom1[] + geom2[]) / ),
severity= distance < clearance/
)
clashes.append(clash)
.clashes.extend(clashes)
clashes
() -> :
(np.(geom1[] >= geom2[])
np.(geom2[] >= geom1[]))
() -> :
tree1 = cKDTree(geom1[])
distances, _ = tree1.query(geom2[], k=)
min_dist = distances.()
min_dist < :
intersection_idx = np.argmin(distances)
{
: ,
: min_dist,
: geom2[][intersection_idx]
}
{: , : min_dist, : }
() -> :
tree1 = cKDTree(geom1[])
distances, _ = tree1.query(geom2[], k=)
distances.()
() -> :
distance < :
distance < :
distance < :
:
() -> pd.DataFrame:
.clashes:
pd.DataFrame()
pd.DataFrame([
{
: c.element1_id,
: c.element1_type,
: c.element1_name,
: c.element2_id,
: c.element2_type,
: c.element2_name,
: c.clash_type,
: c.distance,
: c.location[],
: c.location[],
: c.location[],
: c.severity
}
c .clashes
])
() -> :
df = .get_clash_report()
df.empty:
{: }
{
: (.clashes),
: df[].value_counts().to_dict(),
: df[].value_counts().to_dict(),
: (df[df[] == ]),
: df[].unique().tolist() +
df[].unique().tolist()
}
Clash Sets Configuration
Common Clash Test Sets
CLASH_SETS = {
'structure_vs_mep': {
'group1': ['IfcColumn', 'IfcBeam', 'IfcWall', 'IfcSlab'],
'group2': ['IfcPipeSegment', 'IfcDuctSegment', 'IfcCableSegment'],
'clearance': 0.05,
'description': 'Structural elements vs MEP systems'
},
'piping_vs_hvac': {
'group1': ['IfcPipeSegment', 'IfcPipeFitting'],
'group2': ['IfcDuctSegment', 'IfcDuctFitting'],
'clearance': 0.10,
'description': 'Plumbing vs HVAC conflicts'
},
'doors_clearance': {
'group1': ['IfcDoor'],
'group2': ['IfcColumn', 'IfcWall'],
'clearance': 0.90,
'description': 'Door opening clearances'
},
'electrical_vs_plumbing': {
'group1': ['IfcCableSegment', 'IfcElectricDistributionBoard'],
'group2': ['IfcPipeSegment', 'IfcSanitaryTerminal'],
'clearance': 0.15,
'description':
},
: {
: [],
: [, , ],
: ,
:
}
}
() -> :
clash_sets :
clash_sets = CLASH_SETS
results = {}
test_name, config clash_sets.items():
()
hard = detector.detect_hard_clashes(config[], config[])
soft = detector.detect_soft_clashes(
config[],
config[],
config[]
)
results[test_name] = {
: config[],
: (hard),
: (soft),
: (hard) + (soft)
}
results
Report Generation
Export Clash Report
def export_clash_report(detector: ClashDetector, output_path: str):
"""Export comprehensive clash report to Excel"""
df = detector.get_clash_report()
summary = detector.get_summary()
with pd.ExcelWriter(output_path, engine='openpyxl') as writer:
summary_df = pd.DataFrame([{
'Total Clashes': summary['total'],
'Critical': summary.get('critical_count', 0),
'Hard Clashes': summary['by_type'].get('Hard', 0),
'Soft Clashes': summary['by_type'].get('Soft', 0)
}])
summary_df.to_excel(writer, sheet_name='Summary', index=False)
if not df.empty:
df.to_excel(writer, sheet_name='All_Clashes', index=False)
for severity in ['Critical', 'High', 'Medium', 'Low']:
severity_df = df[df['Severity'] == severity]
if not severity_df.empty:
severity_df.to_excel(writer, sheet_name=severity, index=False)
return output_path
def generate_clash_html_report(detector: ClashDetector, output_path: ):
df = detector.get_clash_report()
summary = detector.get_summary()
html =
(output_path, ) f:
f.write(html)
output_path
Quick Reference
| Clash Type | Description | Typical Clearance |
|---|
| Hard Clash | Physical intersection | 0 mm |
| Soft Clash | Clearance violation | 50-150 mm |
| Workflow | Schedule conflict | N/A |
| Severity | Distance | Action Required |
|---|
| Critical | < 10 mm | Immediate redesign |
| High | 10-50 mm | Priority fix |
| Medium | 50-100 mm | Review needed |
| Low | > 100 mm | Monitor |
Resources
Next Steps
- See
4d-simulation for time-based clash analysis
- See
bim-validation-pipeline for validation workflows
- See
ifc-data-extraction for element data