bim-validation-report
skillGenerate comprehensive BIM model validation reports. Check data quality, completeness, and compliance with standards.
apm::install
apm install @datadrivenconstruction/bim-validation-reportapm::skill.md
---
name: "bim-validation-report"
description: "Generate comprehensive BIM model validation reports. Check data quality, completeness, and compliance with standards."
homepage: "https://datadrivenconstruction.io"
metadata: {"openclaw": {"emoji": "🔍", "os": ["darwin", "linux", "win32"], "homepage": "https://datadrivenconstruction.io", "requires": {"bins": ["python3"]}}}
---
# BIM Validation Report Generator
## Business Case
### Problem Statement
BIM models often have quality issues:
- Missing required properties
- Invalid or inconsistent data
- Non-compliant with project standards
- Incomplete model information
### Solution
Automated BIM validation system that checks models against configurable rules and generates detailed compliance reports.
### Business Value
- **Quality assurance** - Catch issues early
- **Standards compliance** - Meet project requirements
- **Automation** - Reduce manual QC effort
- **Transparency** - Clear validation results
## Technical Implementation
```python
import pandas as pd
from datetime import datetime
from typing import Dict, Any, List, Optional, Callable
from dataclasses import dataclass, field
from enum import Enum
class ValidationSeverity(Enum):
"""Validation issue severity."""
ERROR = "error"
WARNING = "warning"
INFO = "info"
class ValidationStatus(Enum):
"""Overall validation status."""
PASSED = "passed"
PASSED_WITH_WARNINGS = "passed_with_warnings"
FAILED = "failed"
class RuleCategory(Enum):
"""Validation rule categories."""
REQUIRED_PROPERTIES = "required_properties"
DATA_FORMAT = "data_format"
NAMING_CONVENTION = "naming_convention"
GEOMETRIC = "geometric"
CLASSIFICATION = "classification"
RELATIONSHIPS = "relationships"
@dataclass
class ValidationRule:
"""Single validation rule."""
rule_id: str
name: str
category: RuleCategory
description: str
severity: ValidationSeverity
check_function: Callable
applicable_categories: List[str] = field(default_factory=list)
enabled: bool = True
@dataclass
class ValidationIssue:
"""Single validation issue."""
issue_id: str
rule_id: str
rule_name: str
element_id: str
element_name: str
element_category: str
severity: ValidationSeverity
message: str
details: Dict[str, Any] = field(default_factory=dict)
def to_dict(self) -> Dict[str, Any]:
return {
'issue_id': self.issue_id,
'rule_id': self.rule_id,
'rule_name': self.rule_name,
'element_id': self.element_id,
'element_name': self.element_name,
'element_category': self.element_category,
'severity': self.severity.value,
'message': self.message
}
@dataclass
class ValidationReport:
"""Complete validation report."""
project_name: str
model_name: str
validated_at: datetime
status: ValidationStatus
total_elements: int
elements_with_issues: int
issues: List[ValidationIssue]
rules_checked: int
summary_by_severity: Dict[str, int]
summary_by_category: Dict[str, int]
class BIMValidationEngine:
"""BIM model validation engine."""
def __init__(self, project_name: str, model_name: str):
self.project_name = project_name
self.model_name = model_name
self.rules: List[ValidationRule] = []
self.issues: List[ValidationIssue] = []
self._issue_counter = 0
# Load default rules
self._load_default_rules()
def _load_default_rules(self):
"""Load standard validation rules."""
# Required properties rules
self.add_rule(ValidationRule(
rule_id="REQ-001",
name="Element Name Required",
category=RuleCategory.REQUIRED_PROPERTIES,
description="All elements must have a name",
severity=ValidationSeverity.ERROR,
check_function=lambda e: bool(e.get('name'))
))
self.add_rule(ValidationRule(
rule_id="REQ-002",
name="Level Assignment Required",
category=RuleCategory.REQUIRED_PROPERTIES,
description="Elements must be assigned to a level",
severity=ValidationSeverity.WARNING,
check_function=lambda e: bool(e.get('level')),
applicable_categories=["Walls", "Floors", "Doors", "Windows"]
))
self.add_rule(ValidationRule(
rule_id="REQ-003",
name="Material Required",
category=RuleCategory.REQUIRED_PROPERTIES,
description="Structural elements must have material defined",
severity=ValidationSeverity.ERROR,
check_function=lambda e: bool(e.get('material')),
applicable_categories=["Structural Columns", "Structural Framing", "Floors"]
))
# Naming convention rules
self.add_rule(ValidationRule(
rule_id="NAM-001",
name="No Special Characters",
category=RuleCategory.NAMING_CONVENTION,
description="Names should not contain special characters",
severity=ValidationSeverity.WARNING,
check_function=self._check_no_special_chars
))
self.add_rule(ValidationRule(
rule_id="NAM-002",
name="Name Length Check",
category=RuleCategory.NAMING_CONVENTION,
description="Names should be between 3 and 100 characters",
severity=ValidationSeverity.INFO,
check_function=lambda e: 3 <= len(e.get('name', '')) <= 100
))
# Classification rules
self.add_rule(ValidationRule(
rule_id="CLS-001",
name="Classification Code Present",
category=RuleCategory.CLASSIFICATION,
description="Elements should have classification code",
severity=ValidationSeverity.WARNING,
check_function=lambda e: bool(e.get('classification_code') or e.get('uniformat'))
))
# Geometric rules
self.add_rule(ValidationRule(
rule_id="GEO-001",
name="Non-Zero Volume",
category=RuleCategory.GEOMETRIC,
description="3D elements must have non-zero volume",
severity=ValidationSeverity.ERROR,
check_function=lambda e: float(e.get('volume', 0)) > 0,
applicable_categories=["Walls", "Floors", "Structural Columns", "Structural Framing"]
))
self.add_rule(ValidationRule(
rule_id="GEO-002",
name="Valid Bounding Box",
category=RuleCategory.GEOMETRIC,
description="Elements must have valid bounding box",
severity=ValidationSeverity.ERROR,
check_function=self._check_valid_bbox
))
def _check_no_special_chars(self, element: Dict[str, Any]) -> bool:
"""Check name for special characters."""
import re
name = element.get('name', '')
return bool(re.match(r'^[\w\s\-\.]+$', name))
def _check_valid_bbox(self, element: Dict[str, Any]) -> bool:
"""Check for valid bounding box."""
try:
min_x = float(element.get('min_x', 0))
max_x = float(element.get('max_x', 0))
min_y = float(element.get('min_y', 0))
max_y = float(element.get('max_y', 0))
min_z = float(element.get('min_z', 0))
max_z = float(element.get('max_z', 0))
return max_x > min_x and max_y > min_y and max_z > min_z
except (ValueError, TypeError):
return False
def add_rule(self, rule: ValidationRule):
"""Add validation rule."""
self.rules.append(rule)
def add_custom_rule(self, rule_id: str, name: str, category: RuleCategory,
check_function: Callable, severity: ValidationSeverity = ValidationSeverity.WARNING,
description: str = "", categories: List[str] = None):
"""Add custom validation rule."""
rule = ValidationRule(
rule_id=rule_id,
name=name,
category=category,
description=description,
severity=severity,
check_function=check_function,
applicable_categories=categories or []
)
self.add_rule(rule)
def validate_element(self, element: Dict[str, Any]) -> List[ValidationIssue]:
"""Validate single element against all rules."""
issues = []
element_category = element.get('category', '')
for rule in self.rules:
if not rule.enabled:
continue
# Check if rule applies to this category
if rule.applicable_categories and element_category not in rule.applicable_categories:
continue
try:
passed = rule.check_function(element)
if not passed:
self._issue_counter += 1
issue = ValidationIssue(
issue_id=f"ISS-{self._issue_counter:05d}",
rule_id=rule.rule_id,
rule_name=rule.name,
element_id=str(element.get('element_id', '')),
element_name=str(element.get('name', '')),
element_category=element_category,
severity=rule.severity,
message=rule.description
)
issues.append(issue)
except Exception as e:
# Rule check failed
self._issue_counter += 1
issue = ValidationIssue(
issue_id=f"ISS-{self._issue_counter:05d}",
rule_id=rule.rule_id,
rule_name=rule.name,
element_id=str(element.get('element_id', '')),
element_name=str(element.get('name', '')),
element_category=element_category,
severity=ValidationSeverity.ERROR,
message=f"Rule check error: {str(e)}"
)
issues.append(issue)
return issues
def validate_model(self, elements_df: pd.DataFrame) -> ValidationReport:
"""Validate entire BIM model."""
self.issues = []
elements_with_issues = set()
for _, row in elements_df.iterrows():
element = row.to_dict()
element_issues = self.validate_element(element)
if element_issues:
elements_with_issues.add(element.get('element_id'))
self.issues.extend(element_issues)
# Calculate summaries
summary_by_severity = {
'error': sum(1 for i in self.issues if i.severity == ValidationSeverity.ERROR),
'warning': sum(1 for i in self.issues if i.severity == ValidationSeverity.WARNING),
'info': sum(1 for i in self.issues if i.severity == ValidationSeverity.INFO)
}
summary_by_category = {}
for issue in self.issues:
cat = issue.element_category
summary_by_category[cat] = summary_by_category.get(cat, 0) + 1
# Determine overall status
if summary_by_severity['error'] > 0:
status = ValidationStatus.FAILED
elif summary_by_severity['warning'] > 0:
status = ValidationStatus.PASSED_WITH_WARNINGS
else:
status = ValidationStatus.PASSED
return ValidationReport(
project_name=self.project_name,
model_name=self.model_name,
validated_at=datetime.now(),
status=status,
total_elements=len(elements_df),
elements_with_issues=len(elements_with_issues),
issues=self.issues,
rules_checked=len([r for r in self.rules if r.enabled]),
summary_by_severity=summary_by_severity,
summary_by_category=summary_by_category
)
def export_report(self, report: ValidationReport, output_path: str):
"""Export validation report to Excel."""
with pd.ExcelWriter(output_path, engine='openpyxl') as writer:
# Summary sheet
summary_data = {
'Metric': ['Project', 'Model', 'Validated At', 'Status',
'Total Elements', 'Elements with Issues', 'Rules Checked',
'Errors', 'Warnings', 'Info'],
'Value': [report.project_name, report.model_name,
report.validated_at.isoformat(), report.status.value,
report.total_elements, report.elements_with_issues,
report.rules_checked, report.summary_by_severity['error'],
report.summary_by_severity['warning'], report.summary_by_severity['info']]
}
pd.DataFrame(summary_data).to_excel(writer, sheet_name='Summary', index=False)
# Issues sheet
issues_df = pd.DataFrame([i.to_dict() for i in report.issues])
if not issues_df.empty:
issues_df.to_excel(writer, sheet_name='Issues', index=False)
# By Category sheet
cat_df = pd.DataFrame([
{'Category': k, 'Issue Count': v}
for k, v in report.summary_by_category.items()
])
if not cat_df.empty:
cat_df.to_excel(writer, sheet_name='By Category', index=False)
return output_path
def generate_validation_report(elements_df: pd.DataFrame,
project_name: str,
model_name: str,
output_path: str = None) -> ValidationReport:
"""Quick function to generate validation report."""
engine = BIMValidationEngine(project_name, model_name)
report = engine.validate_model(elements_df)
if output_path:
engine.export_report(report, output_path)
return report
```
## Quick Start
```python
# Load BIM elements
elements = pd.read_excel("bim_elements.xlsx")
# Run validation
report = generate_validation_report(
elements,
project_name="Office Tower",
model_name="Architectural Model v3.2",
output_path="validation_report.xlsx"
)
print(f"Status: {report.status.value}")
print(f"Errors: {report.summary_by_severity['error']}")
print(f"Warnings: {report.summary_by_severity['warning']}")
```
## Common Use Cases
### 1. Custom Validation Rules
```python
engine = BIMValidationEngine("Project", "Model")
# Add custom rule
engine.add_custom_rule(
rule_id="CUSTOM-001",
name="Fire Rating Required",
category=RuleCategory.REQUIRED_PROPERTIES,
check_function=lambda e: bool(e.get('fire_rating')),
severity=ValidationSeverity.ERROR,
categories=["Walls", "Doors"]
)
```
### 2. Filter Issues
```python
# Get only errors
errors = [i for i in report.issues if i.severity == ValidationSeverity.ERROR]
# Get issues for specific category
wall_issues = [i for i in report.issues if i.element_category == "Walls"]
```
### 3. Automated QC Pipeline
```python
report = engine.validate_model(elements)
if report.status == ValidationStatus.FAILED:
send_notification("BIM validation failed", report.summary_by_severity)
```
## Resources
- **DDC Book**: Chapter 4.3 - BIM Validation
- **Reference**: ISO 19650, buildingSMART IDS