| name | bim-validation-pipeline |
| description | Build automated BIM validation pipelines for IFC/Revit data. Continuous validation against IDS, LOD requirements, COBie, and project-specific BEP standards. |
| homepage | https://datadrivenconstruction.io |
| metadata | {"openclaw":{"emoji":"🔎","os":["win32"],"homepage":"https://datadrivenconstruction.io","requires":{"bins":"[Truncated]","anyBins":"[Truncated]"}}} |
BIM Validation Pipeline
Overview
Based on DDC methodology (Chapter 4.3), this skill provides automated BIM data validation pipelines. Validate BIM models against Information Delivery Specification (IDS), Level of Development (LOD) requirements, and project standards.
Book Reference: "Автоматический ETL конвейер для валидации данных" / "Automated ETL Pipeline for Data Validation"
"Автоматизированная валидация BIM-данных позволяет выявлять ошибки на ранних стадиях и обеспечивать соответствие требованиям BEP."
— DDC Book, Chapter 4.3
Quick Start
import ifcopenshell
import pandas as pd
ifc_model = ifcopenshell.open("model.ifc")
walls = ifc_model.by_type("IfcWall")
print(f"Total walls: {len(walls)}")
issues = []
for wall in walls:
if not wall.HasAssociations:
issues.append(f"Wall {wall.GlobalId}: No material assigned")
print(f"Issues found: {len(issues)}")
BIM Validation Framework
Core Validator Class
import ifcopenshell
import ifcopenshell.util.element as element_util
import pandas as pd
from dataclasses import dataclass
from typing import List, Dict, Optional
from enum import Enum
class Severity(Enum):
ERROR = "error"
WARNING = "warning"
INFO = "info"
@dataclass
class ValidationIssue:
element_id: str
element_type: str
rule_id: str
severity: Severity
message: str
location: Optional[str] = None
class BIMValidator:
"""Comprehensive BIM model validator"""
def __init__(self, ifc_path: str):
self.model = ifcopenshell.open(ifc_path)
self.issues: List[ValidationIssue] = []
self.stats = {}
def validate_all(self):
"""Run all validation checks"""
self.validate_geometry()
self.validate_properties()
.validate_relationships()
.validate_naming()
.validate_classification()
.get_report()
():
elements_with_geometry = [
e e .model.by_type()
e.Representation
]
element elements_with_geometry:
:
settings = ifcopenshell.geom.settings()
shape = ifcopenshell.geom.create_shape(settings, element)
:
.issues.append(ValidationIssue(
element_id=element.GlobalId,
element_type=element.is_a(),
rule_id=,
severity=Severity.ERROR,
message=
))
.stats[] = (elements_with_geometry)
():
required_psets :
required_psets = {
: [, ],
: [, ],
: [, ],
: [, ]
}
ifc_type, psets required_psets.items():
elements = .model.by_type(ifc_type)
element elements:
element_psets = element_util.get_psets(element)
required_pset psets:
required_pset element_psets:
.issues.append(ValidationIssue(
element_id=element.GlobalId,
element_type=ifc_type,
rule_id=,
severity=Severity.WARNING,
message=
))
():
products = .model.by_type()
product products:
(product, ):
product.ContainedInStructure:
.issues.append(ValidationIssue(
element_id=product.GlobalId,
element_type=product.is_a(),
rule_id=,
severity=Severity.WARNING,
message=
))
(product, ):
has_material = (
rel.is_a()
rel (product.HasAssociations [])
)
has_material product.is_a() [, , ]:
.issues.append(ValidationIssue(
element_id=product.GlobalId,
element_type=product.is_a(),
rule_id=,
severity=Severity.WARNING,
message=
))
():
re
patterns :
patterns = {
: ,
: ,
: ,
:
}
ifc_type, pattern patterns.items():
elements = .model.by_type(ifc_type)
element elements:
name = element.Name
re.(pattern, name, re.IGNORECASE):
.issues.append(ValidationIssue(
element_id=element.GlobalId,
element_type=ifc_type,
rule_id=,
severity=Severity.INFO,
message=
))
():
required_systems :
required_systems = [, , ]
elements = .model.by_type()
element elements:
(element, ):
has_classification = (
rel.is_a()
rel (element.HasAssociations [])
)
has_classification:
.issues.append(ValidationIssue(
element_id=element.GlobalId,
element_type=element.is_a(),
rule_id=,
severity=Severity.INFO,
message=
))
():
by_severity = {s: [] s Severity}
by_type = {}
by_rule = {}
issue .issues:
by_severity[issue.severity].append(issue)
issue.element_type by_type:
by_type[issue.element_type] = []
by_type[issue.element_type].append(issue)
issue.rule_id by_rule:
by_rule[issue.rule_id] = []
by_rule[issue.rule_id].append(issue)
{
: (.issues),
: (by_severity[Severity.ERROR]),
: (by_severity[Severity.WARNING]),
: (by_severity[Severity.INFO]),
: {k: (v) k, v by_type.items()},
: {k: (v) k, v by_rule.items()},
: .issues,
: .stats
}
LOD Validation
Level of Development Checker
class LODValidator:
"""Validate Level of Development (LOD) requirements"""
LOD_REQUIREMENTS = {
'LOD100': {
'geometry': False,
'properties': [],
'description': 'Conceptual'
},
'LOD200': {
'geometry': True,
'approximate_size': True,
'properties': ['Category'],
'description': 'Schematic Design'
},
'LOD300': {
'geometry': True,
'exact_size': True,
'properties': ['Category', 'Material', 'Type'],
'quantities': ['Length', 'Area', 'Volume'],
'description': 'Design Development'
},
'LOD350': {
'geometry': True,
'exact_size': True,
'properties': ['Category', 'Material', 'Type', 'Manufacturer'],
'quantities': ['Length', 'Area', 'Volume', ],
: ,
:
},
: {
: ,
: ,
: [, , , ,
, , ],
: [],
: ,
:
}
}
():
.model = model
.target_lod = target_lod
.requirements = .LOD_REQUIREMENTS.get(target_lod, {})
.results = []
():
issues = []
element_guid = element.GlobalId
psets = element_util.get_psets(element)
.requirements.get():
element.Representation:
issues.append({
: element_guid,
: ,
: .target_lod
})
required_props = .requirements.get(, [])
all_props = {}
pset_name, props psets.items():
all_props.update(props)
prop required_props:
prop all_props all_props[prop] :
issues.append({
: element_guid,
: ,
: .target_lod
})
required_quantities = .requirements.get(, [])
required_quantities != []:
qsets = psets.get(, {})
qty required_quantities:
qty qsets:
issues.append({
: element_guid,
: ,
: .target_lod
})
issues
():
element_types :
element_types = [, , , ,
, , ]
all_issues = []
summary = {}
ifc_type element_types:
elements = .model.by_type(ifc_type)
type_issues = []
element elements:
issues = .validate_element(element)
type_issues.extend(issues)
summary[ifc_type] = {
: (elements),
: (type_issues),
: (((elements) - (type_issues)) /
(elements) * ) elements
}
all_issues.extend(type_issues)
{
: .target_lod,
: (all_issues),
: summary,
: all_issues
}
IDS Validation
Information Delivery Specification
import xml.etree.ElementTree as ET
class IDSValidator:
"""Validate against IDS (Information Delivery Specification)"""
def __init__(self, ids_path: str):
self.ids = self._parse_ids(ids_path)
def _parse_ids(self, path):
"""Parse IDS XML file"""
tree = ET.parse(path)
root = tree.getroot()
specifications = []
for spec in root.findall('.//specification'):
specifications.append({
'name': spec.get('name'),
'applicability': self._parse_facets(spec.find('applicability')),
'requirements': self._parse_facets(spec.find('requirements'))
})
return specifications
def _parse_facets(self, element):
"""Parse IDS facets"""
if element is None:
return []
facets = []
for child in element:
facet = {
'type': child.tag,
'constraints': {}
}
for attr, value in child.attrib.items():
facet['constraints'][attr] = value
facets.append(facet)
facets
():
results = []
spec .ids:
applicable_elements = ._find_applicable_elements(
model, spec[]
)
element applicable_elements:
issues = ._check_requirements(element, spec[])
issues:
results.append({
: spec[],
: element.GlobalId,
: issues
})
results
():
elements = []
facet applicability:
facet[] == :
ifc_type = facet[].get()
ifc_type:
elements.extend(model.by_type(ifc_type))
elements
():
issues = []
psets = element_util.get_psets(element)
req requirements:
req[] == :
pset_name = req[].get()
prop_name = req[].get()
pset_name prop_name:
pset = psets.get(pset_name, {})
prop_name pset:
issues.append()
issues
Pipeline Automation
Automated Validation Pipeline
import os
from datetime import datetime
import json
class BIMValidationPipeline:
"""Automated BIM validation pipeline"""
def __init__(self, config_path=None):
self.config = self._load_config(config_path)
self.results_history = []
def _load_config(self, path):
if path and os.path.exists(path):
with open(path) as f:
return json.load(f)
return {
'lod_target': 'LOD300',
'required_psets': {
'IfcWall': ['Pset_WallCommon'],
'IfcSlab': ['Pset_SlabCommon']
},
'naming_patterns': {},
'fail_on_errors': True,
'warn_threshold': 50
}
def run(self, ifc_path):
"""Run complete validation pipeline"""
start_time = datetime.now()
validator = BIMValidator(ifc_path)
lod_validator = LODValidator(
validator.model,
self.config['lod_target']
)
bim_report = validator.validate_all()
lod_report = lod_validator.validate_model()
result = {
: ifc_path,
: start_time.isoformat(),
: (datetime.now() - start_time).total_seconds(),
: bim_report,
: lod_report,
: ._evaluate_pass(bim_report, lod_report)
}
.results_history.append(result)
result
():
.config[] bim_report[] > :
bim_report[] > .config[]:
():
results = []
path ifc_paths:
:
result = .run(path)
results.append(result)
Exception e:
results.append({
: path,
: (e),
:
})
{
: (results),
: ( r results r.get(, )),
: ( r results r.get(, )),
: results
}
():
.results_history:
latest = .results_history[-]
pd.ExcelWriter(output_path, engine=) writer:
summary = pd.DataFrame({
: [, , , , ],
: [
latest[],
latest[],
latest[],
latest[][],
latest[][]
]
})
summary.to_excel(writer, sheet_name=, index=)
latest[][]:
issues_df = pd.DataFrame([
{
: i.element_id,
: i.element_type,
: i.rule_id,
: i.severity.value,
: i.message
}
i latest[][]
])
issues_df.to_excel(writer, sheet_name=, index=)
output_path
Quick Reference
| Rule ID | Description | Severity |
|---|
| GEO-001 | Invalid/missing geometry | ERROR |
| PROP-001 | Missing PropertySet | WARNING |
| REL-001 | No spatial containment | WARNING |
| MAT-001 | No material assigned | WARNING |
| NAME-001 | Invalid naming convention | INFO |
| CLASS-001 | No classification | INFO |
LOD Requirements Summary
| LOD | Geometry | Properties | Quantities |
|---|
| 100 | No | - | - |
| 200 | Approximate | Category | - |
| 300 | Exact | Material, Type | L, A, V |
| 350 | Exact + connections | Manufacturer | All |
| 400 | Fabrication-ready | All details | All |
Resources
Next Steps
- See
ifc-data-extraction for extracting data from IFC
- See
data-quality-check for general data validation
- See
qto-report for quantity take-off from validated models