IfcOpenShell Validation Workflows
Quick Reference
Critical Warnings
- ALWAYS run
ifcopenshell.validate.validate() with basic mode first (default express_rules=False). NEVER enable EXPRESS WHERE rules on a first pass — they are 10-100x slower on large models.
- ALWAYS pass a configured
logging.Logger instance to validate(). The function logs results; it does NOT return them.
- NEVER assume a model is valid because
validate() completes without raising an exception. Validation issues are logged, not raised. Use LogDetectionHandler or json_logger to detect issues programmatically.
- ALWAYS use
ifctester for IDS (Information Delivery Specification) validation. ifcopenshell.validate checks schema compliance only — it does NOT check project-specific requirements.
- NEVER confuse schema validation with IDS validation. Schema validation verifies the IFC file structure is correct per EXPRESS schema. IDS validation verifies the IFC data meets project information requirements.
- ALWAYS call
add_georeferencing before edit_georeferencing. The IfcMapConversion and IfcProjectedCRS entities MUST exist before editing.
- NEVER use
IfcMapConversionScaled in IFC4 models. The scaled variant is only available in IFC4X3.
- ALWAYS check
model.schema before applying version-specific validation logic. Georeferencing differs between IFC2X3 (property sets) and IFC4+ (dedicated entities).
Decision Tree: Which Validation Approach
What are you validating?
├── IFC file structure and schema compliance?
│ ├── Basic type/attribute checking?
│ │ └── ifcopenshell.validate.validate(model, logger)
│ ├── Full EXPRESS WHERE rules?
│ │ └── ifcopenshell.validate.validate(model, logger, express_rules=True)
│ ├── GUID format only?
│ │ └── ifcopenshell.validate.validate_guid(guid_string)
│ └── File header only?
│ └── ifcopenshell.validate.validate_ifc_header(model, logger)
│
├── Project information requirements (IDS)?
│ ├── Load IDS specification?
│ │ └── ifctester.open("spec.ids")
│ ├── Validate IFC against IDS?
│ │ └── ids.validate(ifc_file)
│ └── Generate validation report?
│ ├── Console → ifctester.reporter.Console(ids)
│ ├── JSON → ifctester.reporter.Json(ids)
│ ├── HTML → ifctester.reporter.Html(ids)
│ ├── BCF → ifctester.reporter.Bcf(ids)
│ └── ODS → ifctester.reporter.Ods(ids)
│
├── Georeferencing correctness?
│ ├── IFC2X3 → Check property sets on IfcProject
│ ├── IFC4 → Check IfcMapConversion + IfcProjectedCRS entities
│ └── IFC4X3 → Check IfcMapConversion or IfcMapConversionScaled
│
└── Custom business rules?
└── Write Python functions using ifcopenshell entity traversal
(see Custom Validation Rules section)
Essential Patterns
Pattern 1: Basic Schema Validation
# IfcOpenShell: all schema versions
import ifcopenshell
import ifcopenshell.validate
import logging
model = ifcopenshell.open("building.ifc")
# Set up logger to capture validation output
logger = logging.getLogger("ifcopenshell.validate")
logger.setLevel(logging.DEBUG)
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter("%(levelname)s: %(message)s"))
logger.addHandler(handler)
# Run basic validation (no EXPRESS WHERE rules)
ifcopenshell.validate.validate(model, logger)
Pattern 2: Programmatic Validation with json_logger
# IfcOpenShell: all schema versions
import ifcopenshell
import ifcopenshell.validate
model = ifcopenshell.open("building.ifc")
# Use json_logger for structured, programmatic access to results
json_log = ifcopenshell.validate.json_logger()
ifcopenshell.validate.validate(model, json_log)
# Iterate validation results
for statement in json_log.statements:
print(f"Level: {statement['level']}, Message: {statement['message']}")
# Check if any issues were found
if json_log.statements:
print(f"Total issues: {len(json_log.statements)}")
else:
print("Model is valid")
Pattern 3: Detect Validation Issues (Pass/Fail Gate)
# IfcOpenShell: all schema versions
import ifcopenshell
import ifcopenshell.validate
import logging
model = ifcopenshell.open("building.ifc")
logger = logging.getLogger("ifcopenshell.validate")
logger.setLevel(logging.WARNING)
# Add detection handler to check if ANY issues exist
detection_handler = ifcopenshell.validate.LogDetectionHandler()
logger.addHandler(detection_handler)
ifcopenshell.validate.validate(model, logger)
if detection_handler.message_logged:
print("FAIL: Validation issues found")
else:
print("PASS: Model is valid")
Pattern 4: IDS Validation with ifctester
# IfcOpenShell: all schema versions
import ifcopenshell
import ifctester
import ifctester.ids
import ifctester.reporter
# Step 1: Load the IDS specification
ids = ifctester.open("requirements.ids")
# Step 2: Load the IFC file
ifc_file = ifcopenshell.open("building.ifc")
# Step 3: Validate IFC against IDS
ids.validate(ifc_file)
# Step 4: Report results
reporter = ifctester.reporter.Console(ids)
reporter.report()
# Or generate HTML report
html_reporter = ifctester.reporter.Html(ids)
html_reporter.report()
html_reporter.to_file("validation_report.html")
Pattern 5: Georeferencing Validation
# IfcOpenShell: IFC4 / IFC4X3
import ifcopenshell
model = ifcopenshell.open("building.ifc")
# Check schema version for georeferencing method
if model.schema == "IFC2X3":
# IFC2X3: Georeferencing stored as property sets on IfcProject
project = model.by_type("IfcProject")[0]
# Check for ePSet_MapConversion and ePSet_ProjectedCRS property sets
psets = ifcopenshell.util.element.get_psets(project)
has_georef = "ePSet_MapConversion" in psets
else:
# IFC4 / IFC4X3: Dedicated entities
map_conversions = model.by_type("IfcMapConversion")
projected_crs = model.by_type("IfcProjectedCRS")
has_georef = len(map_conversions) > 0 and len(projected_crs) > 0
if model.schema == "IFC4X3":
# Also check for IfcMapConversionScaled (IFC4X3 only)
scaled = model.by_type("IfcMapConversionScaled")
has_georef = has_georef or len(scaled) > 0
if has_georef:
print("Georeferencing is present")
else:
print("WARNING: No georeferencing found")
IDS Validation (Information Delivery Specification)
What is IDS?
IDS is a buildingSMART standard (ISO 7817-3) that defines machine-readable information requirements for IFC models. An IDS file specifies:
- Applicability: Which IFC entities a requirement applies to (e.g., all IfcWall instances)
- Requirements: What data those entities must contain (e.g., must have a FireRating property)
IDS Facet Types
| Facet |
Purpose |
Example |
Entity |
Filter by IFC class and predefined type |
All IfcWall with PredefinedType=SOLIDWALL |
Attribute |
Check entity attribute values |
Name must match pattern "W-*" |
Classification |
Check classification references |
Must have Uniclass 2015 reference |
Property |
Check property set values |
Pset_WallCommon.FireRating must exist |
Material |
Check material assignments |
Must have material assigned |
PartOf |
Check spatial/aggregation relationships |
Must be contained in IfcBuildingStorey |
Creating IDS Programmatically
# IfcOpenShell: all schema versions
import ifctester.ids
# Create new IDS document
ids = ifctester.ids.Ids(
title="Project Requirements",
description="Minimum information requirements for structural review",
author="QA Team",
version="1.0"
)
# Create a specification: All walls must have fire rating
spec = ifctester.ids.Specification(
name="Wall Fire Rating Required",
ifcVersion=["IFC4"],
description="All load-bearing walls must declare fire rating"
)
# Add to IDS
ids.specifications_.append(spec)
# Export to IDS XML
ids.to_xml("project_requirements.ids")
IDS Reporter Types
| Reporter |
Output |
Use Case |
Console |
Terminal text with color |
Quick interactive checks |
Json |
Structured JSON data |
CI/CD pipelines, API integration |
Html |
Formatted HTML page |
Stakeholder reports |
Bcf |
BCF issue file |
BIM coordination (links to model elements) |
Ods |
Spreadsheet (ODS) |
Data analysis, Excel-compatible review |
Txt |
Plain text |
Log files, archival |
Georeferencing: IFC2X3 vs IFC4+ Differences
Version Comparison
| Feature |
IFC2X3 |
IFC4 |
IFC4X3 |
| Map conversion |
Property set on IfcProject |
IfcMapConversion entity |
IfcMapConversion or IfcMapConversionScaled |
| CRS definition |
Property set |
IfcProjectedCRS entity |
IfcProjectedCRS entity |
| MapUnit attribute |
String (full unit name) |
IfcNamedUnit object |
IfcNamedUnit object |
| Removal method |
Remove property sets |
Remove entities |
Remove entities |
| API support |
Manual property sets |
georeference.* API |
georeference.* API |
Georeferencing API (IFC4+)
| Function |
Purpose |
georeference.add_georeferencing |
Create empty IfcMapConversion + IfcProjectedCRS |
georeference.edit_georeferencing |
Set coordinate operation and CRS parameters |
georeference.edit_true_north |
Set true north orientation (degrees or vector) |
georeference.edit_wcs |
Adjust world coordinate system origin |
georeference.remove_georeferencing |
Remove all georeferencing data |
Georeferencing Anti-Patterns
- NEVER set georeferencing without consulting the project surveyor. Incorrect Eastings/Northings place the building at the wrong location on Earth.
- NEVER confuse Project North with True North.
XAxisAbscissa/XAxisOrdinate in the coordinate operation define Project North rotation. True North is set separately via edit_true_north.
- NEVER use
IfcMapConversionScaled in IFC4 models. It exists only in IFC4X3.
- ALWAYS use EPSG codes in the format
"EPSG:XXXXX" for the CRS Name attribute.
- NEVER move the WCS from origin (0,0,0) without a specific surveying reason. This affects all geometry in the model.
Custom Validation Rules
Pattern: Property Set Completeness Check
# IfcOpenShell: all schema versions
import ifcopenshell
import ifcopenshell.util.element
def validate_required_psets(model, ifc_class, required_psets):
"""Validate that all entities of a type have required property sets."""
errors = []
elements = model.by_type(ifc_class)
for element in elements:
psets = ifcopenshell.util.element.get_psets(element)
for pset_name, required_props in required_psets.items():
if pset_name not in psets:
errors.append(f"#{element.id()} {element.Name}: Missing {pset_name}")
continue
for prop in required_props:
if prop not in psets[pset_name]:
errors.append(
f"#{element.id()} {element.Name}: "
f"Missing {pset_name}.{prop}"
)
return errors
# Usage
model = ifcopenshell.open("building.ifc")
errors = validate_required_psets(model, "IfcWall", {
"Pset_WallCommon": ["IsExternal", "LoadBearing", "FireRating"],
})
for error in errors:
print(f"ERROR: {error}")
Pattern: Spatial Structure Validation
# IfcOpenShell: all schema versions
import ifcopenshell
import ifcopenshell.util.element
def validate_spatial_containment(model):
"""Check that all physical elements are spatially contained."""
errors = []
for element in model.by_type("IfcElement"):
container = ifcopenshell.util.element.get_container(element)
if container is None:
errors.append(
f"#{element.id()} {element.is_a()} '{element.Name}': "
f"Not contained in any spatial element"
)
return errors
Validation Pipeline Pattern
Automated QA Pipeline
# IfcOpenShell: all schema versions
import ifcopenshell
import ifcopenshell.validate
import ifctester
import ifctester.reporter
import logging
import sys
def run_validation_pipeline(ifc_path, ids_path=None):
"""Run complete validation pipeline: schema + IDS + custom rules."""
results = {"schema": None, "ids": None, "custom": []}
# Phase 1: Schema validation
model = ifcopenshell.open(ifc_path)
json_log = ifcopenshell.validate.json_logger()
ifcopenshell.validate.validate(model, json_log)
results["schema"] = {
"passed": len(json_log.statements) == 0,
"issues": len(json_log.statements),
"details": json_log.statements
}
# Phase 2: IDS validation (if specification provided)
if ids_path:
ids = ifctester.open(ids_path)
ids.validate(model)
json_reporter = ifctester.reporter.Json(ids)
json_reporter.report()
results["ids"] = json_reporter.to_string()
# Phase 3: Custom rules
for element in model.by_type("IfcElement"):
container = ifcopenshell.util.element.get_container(element)
if container is None:
results["custom"].append(
f"#{element.id()} {element.is_a()}: No spatial container"
)
return results
# Usage
results = run_validation_pipeline("building.ifc", "requirements.ids")
if not results["schema"]["passed"]:
print(f"Schema issues: {results['schema']['issues']}")
sys.exit(1)
Command-Line Validation
ifcopenshell.validate CLI
# Basic validation
python -m ifcopenshell.validate model.ifc
# With EXPRESS WHERE rules
python -m ifcopenshell.validate model.ifc --rules
# JSON output
python -m ifcopenshell.validate model.ifc --json
# Show attribute field positions in error messages
python -m ifcopenshell.validate model.ifc --fields
ifctester CLI
# Validate IFC against IDS
python -m ifctester model.ifc requirements.ids
# Generate HTML report
python -m ifctester model.ifc requirements.ids --reporter Html --output report.html
Dependencies
- ifcos-syntax-api — For
ifcopenshell.api.run() invocation patterns and georeference.* API functions
- ifcos-syntax-fileio — For
ifcopenshell.open(), ifcopenshell.file(), and file I/O patterns
Reference Links
- Validation API Signatures — Complete function signatures for validate, ifctester, and georeference modules
- Working Code Examples — End-to-end validation workflow examples
- Anti-Patterns — Common validation mistakes and how to avoid them