
Ifcos Impl Validation
- 2 installs
- 30 repo stars
- Updated July 8, 2026
- openaec-foundation/blender-bonsai-ifcopenshell-sverchok-claude-skill-package
Helps with ai & agent building tasks.
About
ifcos-impl-validation is a Claude Code skill for ai & agent building. It helps you ship faster with AI-assisted development.
- ifcos-impl-validation
- AI & Agent Building
- AI-coding skill
Ifcos Impl Validation by the numbers
- 2 all-time installs (skills.sh)
- Ranked #13,958 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/openaec-foundation/blender-bonsai-ifcopenshell-sverchok-claude-skill-package --skill ifcos-impl-validationAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 2 |
|---|---|
| repo stars | ★ 30 |
| Last updated | July 8, 2026 |
| Repository | openaec-foundation/blender-bonsai-ifcopenshell-sverchok-claude-skill-package ↗ |
What it does
Helps with ai & agent building tasks.
Files
IfcOpenShell Validation Workflows
Quick Reference
Critical Warnings
- ALWAYS run
ifcopenshell.validate.validate()with basic mode first (defaultexpress_rules=False). NEVER enable EXPRESS WHERE rules on a first pass — they are 10-100x slower on large models. - ALWAYS pass a configured
logging.Loggerinstance tovalidate(). 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. UseLogDetectionHandlerorjson_loggerto detect issues programmatically. - ALWAYS use
ifctesterfor IDS (Information Delivery Specification) validation.ifcopenshell.validatechecks 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_georeferencingbeforeedit_georeferencing. The IfcMapConversion and IfcProjectedCRS entities MUST exist before editing. - NEVER use
IfcMapConversionScaledin IFC4 models. The scaled variant is only available in IFC4X3. - ALWAYS check
model.schemabefore 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/XAxisOrdinatein the coordinate operation define Project North rotation. True North is set separately viaedit_true_north. - NEVER use
IfcMapConversionScaledin 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 --fieldsifctester 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 andgeoreference.*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
Validation Anti-Patterns
Anti-Pattern 1: Enabling EXPRESS WHERE Rules on First Pass
WRONG:
# Running full EXPRESS WHERE rules immediately on an untested model
ifcopenshell.validate.validate(model, logger, express_rules=True)
# This is 10-100x slower than basic validation and may take hours on large modelsCORRECT:
# Phase 1: Run basic validation first (fast)
json_log = ifcopenshell.validate.json_logger()
ifcopenshell.validate.validate(model, json_log)
if not json_log.statements:
# Phase 2: Only run EXPRESS rules after basic validation passes
ifcopenshell.validate.validate(model, json_log, express_rules=True)
else:
print("Fix basic issues first before running EXPRESS WHERE rules")Why: EXPRESS WHERE rule evaluation requires the C++ backend to evaluate EXPRESS expressions. On models with thousands of entities, this adds 10-100x processing time. Running basic validation first catches type errors, cardinality violations, and format issues at minimal cost. Only invest in WHERE rule checking after the cheap checks pass.
---
Anti-Pattern 2: Assuming validate() Raises Exceptions on Invalid Models
WRONG:
try:
ifcopenshell.validate.validate(model, logger)
print("Model is valid!") # WRONG: validate() does not raise on invalid models
except Exception:
print("Model is invalid!")CORRECT:
# Option A: Use LogDetectionHandler
detection = ifcopenshell.validate.LogDetectionHandler()
logger.addHandler(detection)
ifcopenshell.validate.validate(model, logger)
if detection.message_logged:
print("Model has issues")
else:
print("Model is valid")
# Option B: Use json_logger
json_log = ifcopenshell.validate.json_logger()
ifcopenshell.validate.validate(model, json_log)
if json_log.statements:
print(f"Model has {len(json_log.statements)} issues")
else:
print("Model is valid")Why: validate() emits validation results via the logger. It does NOT raise exceptions for validation failures. An exception-based approach will always report the model as valid, even when it has schema violations. ALWAYS use LogDetectionHandler or json_logger to detect issues programmatically.
---
Anti-Pattern 3: Confusing Schema Validation with IDS Validation
WRONG:
# Checking project-specific requirements with ifcopenshell.validate
ifcopenshell.validate.validate(model, logger)
# This does NOT check if walls have FireRating properties
# This does NOT check if elements have correct classification references
# This does NOT check if naming conventions are followedCORRECT:
# Schema compliance → ifcopenshell.validate
ifcopenshell.validate.validate(model, logger)
# Project information requirements → ifctester with IDS
ids = ifctester.open("project_requirements.ids")
ids.validate(model)
reporter = ifctester.reporter.Console(ids)
reporter.report()Why: ifcopenshell.validate checks whether the IFC file conforms to the EXPRESS schema (correct types, cardinality, valid enumerations). It does NOT verify project-specific information requirements like "all walls must have a fire rating" or "all elements must be classified with Uniclass 2015". For project requirements, ALWAYS use ifctester with an IDS specification.
---
Anti-Pattern 4: Using IfcMapConversionScaled in IFC4 Models
WRONG:
# IFC4 model — IfcMapConversionScaled does NOT exist in IFC4
model = ifcopenshell.file(schema="IFC4")
ifcopenshell.api.run("georeference.add_georeferencing", model,
ifc_class="IfcMapConversionScaled") # ERROR: Not available in IFC4CORRECT:
# IFC4 — use IfcMapConversion only
model = ifcopenshell.file(schema="IFC4")
ifcopenshell.api.run("georeference.add_georeferencing", model,
ifc_class="IfcMapConversion")
# IFC4X3 — IfcMapConversionScaled IS available
model_infra = ifcopenshell.file(schema="IFC4X3")
ifcopenshell.api.run("georeference.add_georeferencing", model_infra,
ifc_class="IfcMapConversionScaled")Why: IfcMapConversionScaled is an IFC4X3 addition for infrastructure projects that need a scale factor in the map conversion. It does NOT exist in the IFC4 schema. Attempting to create it in an IFC4 model causes a schema error. ALWAYS check the target schema version before using version-specific entity classes.
---
Anti-Pattern 5: Editing Georeferencing Before Adding It
WRONG:
# Trying to edit georeferencing that does not exist yet
model = ifcopenshell.file(schema="IFC4")
ifcopenshell.api.run("georeference.edit_georeferencing", model,
projected_crs={"Name": "EPSG:28992"},
coordinate_operation={"Eastings": 155000.0, "Northings": 463000.0})
# ERROR: No IfcMapConversion or IfcProjectedCRS entity exists to editCORRECT:
# Step 1: Create the entities first
ifcopenshell.api.run("georeference.add_georeferencing", model)
# Step 2: Then edit them
ifcopenshell.api.run("georeference.edit_georeferencing", model,
projected_crs={"Name": "EPSG:28992"},
coordinate_operation={"Eastings": 155000.0, "Northings": 463000.0})Why: edit_georeferencing modifies existing IfcMapConversion and IfcProjectedCRS entities. If these entities do not exist, the edit call fails. ALWAYS call add_georeferencing first to create the empty entities, then edit_georeferencing to populate them.
---
Anti-Pattern 6: Confusing Project North with True North
WRONG:
# Setting true north via coordinate_operation (WRONG: this is Project North)
ifcopenshell.api.run("georeference.edit_georeferencing", model,
coordinate_operation={
"XAxisAbscissa": cos(radians(true_north_angle)), # WRONG
"XAxisOrdinate": sin(radians(true_north_angle)), # WRONG
})CORRECT:
from math import cos, sin, radians
# Project North rotation → coordinate_operation (XAxisAbscissa/XAxisOrdinate)
project_north_rotation = -5.0 # degrees from Grid North to Project North
ifcopenshell.api.run("georeference.edit_georeferencing", model,
coordinate_operation={
"XAxisAbscissa": cos(radians(project_north_rotation)),
"XAxisOrdinate": sin(radians(project_north_rotation)),
})
# True North → separate API call
true_north_angle = 5.0 # degrees anticlockwise from Y-axis
ifcopenshell.api.run("georeference.edit_true_north", model,
true_north=true_north_angle)Why: Project North (grid rotation in the coordinate operation) and True North (geographic north for solar analysis) are distinct concepts in IFC. XAxisAbscissa/XAxisOrdinate define the rotation from Grid North to Project North. True North is set independently via edit_true_north. Confusing them results in incorrect solar studies, incorrect map alignment, or both.
---
Anti-Pattern 7: Moving WCS Without Surveying Reason
WRONG:
# Moving WCS to "center the model" — this is almost never correct
ifcopenshell.api.run("georeference.edit_wcs", model,
x=500.0, y=300.0, z=0.0)
# ALL local placements are now offset by (500, 300, 0)CORRECT:
# Keep WCS at origin (default) unless surveyor explicitly requires it
ifcopenshell.api.run("georeference.edit_wcs", model,
x=0.0, y=0.0, z=0.0, rotation=0.0, is_si=True)Why: The World Coordinate System (WCS) origin affects ALL geometric placements in the model. Moving it without a specific surveying requirement causes all local placements to shift by the WCS offset. This creates subtle coordinate errors that are difficult to diagnose. NEVER move the WCS from (0,0,0) unless the project surveyor provides explicit instructions.
---
Anti-Pattern 8: Not Checking Schema Before Version-Specific Validation
WRONG:
# Assuming IFC4+ entities exist in every model
model = ifcopenshell.open("model.ifc")
map_conv = model.by_type("IfcMapConversion") # Fails silently on IFC2X3
if not map_conv:
print("No georeferencing") # WRONG: IFC2X3 uses property sets, not entitiesCORRECT:
model = ifcopenshell.open("model.ifc")
if model.schema == "IFC2X3":
# IFC2X3: georeferencing is in property sets
psets = ifcopenshell.util.element.get_psets(model.by_type("IfcProject")[0])
has_georef = "ePSet_MapConversion" in psets
elif model.schema in ("IFC4", "IFC4X3"):
# IFC4+: georeferencing uses dedicated entities
has_georef = len(model.by_type("IfcMapConversion")) > 0Why: IFC2X3, IFC4, and IFC4X3 represent georeferencing differently. IFC2X3 uses property sets (ePSet_MapConversion, ePSet_ProjectedCRS) on IfcProject. IFC4+ uses dedicated entities (IfcMapConversion, IfcProjectedCRS). ALWAYS check model.schema before applying version-specific validation logic.
---
Anti-Pattern 9: Validating Without Logging Configuration
WRONG:
import ifcopenshell.validate
import logging
logger = logging.getLogger("ifcopenshell.validate")
# No handler configured — validation output is silently discarded
ifcopenshell.validate.validate(model, logger)
# No output visible, no way to detect issuesCORRECT:
import ifcopenshell.validate
import logging
logger = logging.getLogger("ifcopenshell.validate")
logger.setLevel(logging.DEBUG)
# Add at least one handler to capture output
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter("%(levelname)s: %(message)s"))
logger.addHandler(handler)
ifcopenshell.validate.validate(model, logger)Why: Python's logging module discards messages if no handler is configured. If you pass a bare logger to validate() without adding a handler and setting the level, all validation output is silently lost. ALWAYS configure at least one handler and set the appropriate log level before calling validate(). Alternatively, use json_logger() which collects results internally without requiring handler configuration.
---
Anti-Pattern 10: Running IDS Validation Without Checking IFC Version Compatibility
WRONG:
ids = ifctester.open("requirements.ids") # Written for IFC4
ifc_file = ifcopenshell.open("old_model.ifc") # IFC2X3 file
ids.validate(ifc_file)
# May produce misleading results if IDS specs target IFC4-only entitiesCORRECT:
ids = ifctester.open("requirements.ids")
ifc_file = ifcopenshell.open("old_model.ifc")
# Check version compatibility per specification
for spec in ids.specifications_:
if spec.ifcVersion and ifc_file.schema not in spec.ifcVersion:
print(f"WARNING: Spec '{spec.name}' targets {spec.ifcVersion}, "
f"but model is {ifc_file.schema}")
# Run validation with version filtering
ids.validate(ifc_file, should_filter_version=True)Why: IDS specifications can target specific IFC versions. Running an IFC4-targeted specification against an IFC2X3 model produces misleading results — entities may not exist, property sets may differ, and relationships may be structured differently. ALWAYS check version compatibility and use should_filter_version=True to skip specifications that do not match the model's schema version.
Validation Workflow Code Examples
Example 1: Complete Schema Validation with Logging
# IfcOpenShell — all schema versions
import ifcopenshell
import ifcopenshell.validate
import logging
def validate_schema(ifc_path):
"""Run schema validation and return structured results."""
model = ifcopenshell.open(ifc_path)
print(f"Schema: {model.schema}, Entities: {len(model)}")
# Option A: Use json_logger for programmatic access
json_log = ifcopenshell.validate.json_logger()
ifcopenshell.validate.validate(model, json_log)
warnings = [s for s in json_log.statements if s["level"] == "WARNING"]
errors = [s for s in json_log.statements if s["level"] == "ERROR"]
print(f"Warnings: {len(warnings)}, Errors: {len(errors)}")
for error in errors:
print(f" ERROR: {error['message']}")
return {"warnings": warnings, "errors": errors, "valid": len(errors) == 0}
result = validate_schema("building.ifc")Example 2: Schema Validation with Pass/Fail Gate
# IfcOpenShell — all schema versions
import ifcopenshell
import ifcopenshell.validate
import logging
import sys
def validate_or_fail(ifc_path):
"""Validate and exit with code 1 if issues found."""
model = ifcopenshell.open(ifc_path)
logger = logging.getLogger("ifcopenshell.validate")
logger.setLevel(logging.WARNING)
stream_handler = logging.StreamHandler()
stream_handler.setFormatter(logging.Formatter("%(levelname)s: %(message)s"))
logger.addHandler(stream_handler)
detection = ifcopenshell.validate.LogDetectionHandler()
logger.addHandler(detection)
ifcopenshell.validate.validate(model, logger)
if detection.message_logged:
print("VALIDATION FAILED")
sys.exit(1)
else:
print("VALIDATION PASSED")
validate_or_fail("building.ifc")Example 3: EXPRESS WHERE Rule Validation (Two-Phase)
# IfcOpenShell — all schema versions
import ifcopenshell
import ifcopenshell.validate
def two_phase_validation(ifc_path):
"""Run basic validation first, then EXPRESS rules if basic passes."""
model = ifcopenshell.open(ifc_path)
# Phase 1: Basic schema validation (fast)
basic_log = ifcopenshell.validate.json_logger()
ifcopenshell.validate.validate(model, basic_log)
if basic_log.statements:
print(f"Phase 1 FAILED: {len(basic_log.statements)} basic issues found")
print("Fix basic issues before running EXPRESS WHERE rules")
for s in basic_log.statements[:10]: # Show first 10
print(f" {s['level']}: {s['message']}")
return False
print("Phase 1 PASSED: No basic issues")
# Phase 2: EXPRESS WHERE rules (slow, 10-100x longer)
print("Running EXPRESS WHERE rules...")
express_log = ifcopenshell.validate.json_logger()
ifcopenshell.validate.validate(model, express_log, express_rules=True)
if express_log.statements:
print(f"Phase 2 FAILED: {len(express_log.statements)} WHERE rule violations")
for s in express_log.statements[:10]:
print(f" {s['level']}: {s['message']}")
return False
print("Phase 2 PASSED: All EXPRESS WHERE rules satisfied")
return True
two_phase_validation("building.ifc")Example 4: GUID Validation
# IfcOpenShell — all schema versions
import ifcopenshell
import ifcopenshell.validate
def validate_all_guids(ifc_path):
"""Check that all GlobalIds in the model are valid."""
model = ifcopenshell.open(ifc_path)
invalid_guids = []
for entity in model.by_type("IfcRoot"):
guid = entity.GlobalId
error = ifcopenshell.validate.validate_guid(guid)
if error is not None:
invalid_guids.append({
"entity": f"#{entity.id()} {entity.is_a()}",
"guid": guid,
"error": error
})
if invalid_guids:
print(f"Found {len(invalid_guids)} invalid GUIDs:")
for item in invalid_guids:
print(f" {item['entity']}: '{item['guid']}' — {item['error']}")
else:
print("All GUIDs are valid")
return invalid_guids
validate_all_guids("building.ifc")Example 5: IDS Validation — Load and Validate
# IfcOpenShell — all schema versions
import ifcopenshell
import ifctester
import ifctester.reporter
def validate_against_ids(ifc_path, ids_path):
"""Validate IFC model against IDS specification."""
# Load IDS specification
ids = ifctester.open(ids_path)
# Load IFC model
ifc_file = ifcopenshell.open(ifc_path)
# Run validation
ids.validate(ifc_file)
# Console report
console = ifctester.reporter.Console(ids)
console.report()
# Check overall results
for spec in ids.specifications_:
status = "PASS" if spec.status else "FAIL"
print(f" [{status}] {spec.name}")
if not spec.status:
print(f" Failed entities: {len(spec.failed_entities_)}")
print(f" Passed entities: {len(spec.passed_entities_)}")
validate_against_ids("building.ifc", "requirements.ids")Example 6: IDS Validation with Multiple Report Formats
# IfcOpenShell — all schema versions
import ifcopenshell
import ifctester
import ifctester.reporter
def validate_and_report(ifc_path, ids_path, output_dir="."):
"""Validate and generate reports in multiple formats."""
ids = ifctester.open(ids_path)
ifc_file = ifcopenshell.open(ifc_path)
ids.validate(ifc_file)
# HTML report for stakeholders
html = ifctester.reporter.Html(ids)
html.report()
html.to_file(f"{output_dir}/validation_report.html")
# JSON report for CI/CD
json_rep = ifctester.reporter.Json(ids)
json_rep.report()
json_rep.to_file(f"{output_dir}/validation_report.json")
# BCF report for BIM coordination
bcf = ifctester.reporter.Bcf(ids)
bcf.report()
bcf.to_file(f"{output_dir}/validation_issues.bcf")
# ODS report for data review
ods = ifctester.reporter.Ods(ids, excel_safe=True)
ods.report()
ods.to_file(f"{output_dir}/validation_report.ods")
print(f"Reports generated in {output_dir}/")
validate_and_report("building.ifc", "requirements.ids", "./reports")Example 7: Create IDS Specification Programmatically
# IfcOpenShell — all schema versions
import ifctester.ids
# Create IDS document
ids = ifctester.ids.Ids(
title="Structural Review Requirements",
description="Minimum information requirements for structural BIM review",
author="QA Department",
version="2.0",
purpose="Design review",
milestone="LOD 300"
)
# Specification 1: All walls must have fire rating
spec1 = ifctester.ids.Specification(
name="Wall Fire Rating",
ifcVersion=["IFC4", "IFC4X3"],
description="All load-bearing walls must declare a fire rating property"
)
ids.specifications_.append(spec1)
# Specification 2: All spaces must have names
spec2 = ifctester.ids.Specification(
name="Space Naming",
ifcVersion=["IFC2X3", "IFC4", "IFC4X3"],
description="All spaces must have a meaningful Name attribute"
)
ids.specifications_.append(spec2)
# Export to IDS XML file
ids.to_xml("structural_review.ids")
print(f"Created IDS with {len(ids.specifications_)} specifications")
# Also get as string
xml_str = ids.to_string()Example 8: Georeferencing Setup and Validation (IFC4)
# IfcOpenShell — IFC4
import ifcopenshell
import ifcopenshell.api
from math import cos, sin, radians
model = ifcopenshell.api.run("project.create_file", version="IFC4")
project = ifcopenshell.api.run("root.create_entity", model,
ifc_class="IfcProject", name="Example Project")
ifcopenshell.api.run("unit.assign_unit", model)
# Set up georeferencing
ifcopenshell.api.run("georeference.add_georeferencing", model)
ifcopenshell.api.run("georeference.edit_georeferencing", model,
projected_crs={"Name": "EPSG:28992"}, # Amersfoort / RD New
coordinate_operation={
"Eastings": 155000.0,
"Northings": 463000.0,
"OrthogonalHeight": 0.0,
"XAxisAbscissa": cos(radians(-5.0)),
"XAxisOrdinate": sin(radians(-5.0)),
"Scale": 1.0
})
ifcopenshell.api.run("georeference.edit_true_north", model,
true_north=5.0)
# Validate georeferencing
map_conv = model.by_type("IfcMapConversion")
proj_crs = model.by_type("IfcProjectedCRS")
assert len(map_conv) == 1, "Expected exactly one IfcMapConversion"
assert len(proj_crs) == 1, "Expected exactly one IfcProjectedCRS"
assert proj_crs[0].Name == "EPSG:28992", "CRS name mismatch"
print("Georeferencing validated successfully")Example 9: Georeferencing Validation Across Schema Versions
# IfcOpenShell — all schema versions
import ifcopenshell
import ifcopenshell.util.element
def validate_georeferencing(ifc_path):
"""Validate georeferencing based on schema version."""
model = ifcopenshell.open(ifc_path)
schema = model.schema
errors = []
if schema == "IFC2X3":
# IFC2X3: Check property sets on IfcProject
projects = model.by_type("IfcProject")
if not projects:
errors.append("No IfcProject found")
return errors
psets = ifcopenshell.util.element.get_psets(projects[0])
if "ePSet_MapConversion" not in psets:
errors.append("Missing ePSet_MapConversion property set on IfcProject")
if "ePSet_ProjectedCRS" not in psets:
errors.append("Missing ePSet_ProjectedCRS property set on IfcProject")
if "ePSet_MapConversion" in psets:
mc = psets["ePSet_MapConversion"]
required_keys = ["Eastings", "Northings", "OrthogonalHeight"]
for key in required_keys:
if key not in mc:
errors.append(f"ePSet_MapConversion missing '{key}'")
elif schema in ("IFC4", "IFC4X3"):
# IFC4/IFC4X3: Check dedicated entities
map_conversions = model.by_type("IfcMapConversion")
projected_crs = model.by_type("IfcProjectedCRS")
if schema == "IFC4X3":
scaled = model.by_type("IfcMapConversionScaled")
map_conversions = list(map_conversions) + list(scaled)
if not map_conversions:
errors.append("No IfcMapConversion entity found")
elif len(map_conversions) > 1:
errors.append(f"Multiple IfcMapConversion entities ({len(map_conversions)})")
if not projected_crs:
errors.append("No IfcProjectedCRS entity found")
else:
crs = projected_crs[0]
if not crs.Name or not crs.Name.startswith("EPSG:"):
errors.append(f"IfcProjectedCRS.Name should use EPSG format, got: '{crs.Name}'")
# Check for IFC4X3-only entities in IFC4
if schema == "IFC4":
scaled = model.by_type("IfcMapConversionScaled")
if scaled:
errors.append("IfcMapConversionScaled found in IFC4 model (only valid in IFC4X3)")
if errors:
print(f"Georeferencing validation FAILED ({len(errors)} issues):")
for error in errors:
print(f" - {error}")
else:
print("Georeferencing validation PASSED")
return errors
validate_georeferencing("building.ifc")Example 10: Property Set Completeness Validation
# IfcOpenShell — all schema versions
import ifcopenshell
import ifcopenshell.util.element
def validate_pset_completeness(ifc_path, rules):
"""
Validate property set completeness based on rules.
Args:
ifc_path: Path to IFC file
rules: Dict mapping IFC class to required {pset_name: [property_names]}
"""
model = ifcopenshell.open(ifc_path)
all_errors = []
for ifc_class, required_psets in rules.items():
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:
all_errors.append({
"entity": f"#{element.id()} {element.is_a()} '{element.Name}'",
"issue": f"Missing property set: {pset_name}"
})
continue
for prop_name in required_props:
if prop_name not in psets[pset_name]:
all_errors.append({
"entity": f"#{element.id()} {element.is_a()} '{element.Name}'",
"issue": f"Missing property: {pset_name}.{prop_name}"
})
elif psets[pset_name][prop_name] is None:
all_errors.append({
"entity": f"#{element.id()} {element.is_a()} '{element.Name}'",
"issue": f"Null value: {pset_name}.{prop_name}"
})
return all_errors
# Define validation rules
rules = {
"IfcWall": {
"Pset_WallCommon": ["IsExternal", "LoadBearing", "FireRating"],
},
"IfcSlab": {
"Pset_SlabCommon": ["IsExternal", "LoadBearing"],
},
"IfcDoor": {
"Pset_DoorCommon": ["IsExternal", "FireRating"],
},
}
errors = validate_pset_completeness("building.ifc", rules)
if errors:
print(f"Found {len(errors)} property completeness issues:")
for e in errors:
print(f" {e['entity']}: {e['issue']}")Example 11: Spatial Containment Validation
# IfcOpenShell — all schema versions
import ifcopenshell
import ifcopenshell.util.element
def validate_spatial_structure(ifc_path):
"""Validate that spatial hierarchy and containment are correct."""
model = ifcopenshell.open(ifc_path)
errors = []
# Check 1: IfcProject exists
projects = model.by_type("IfcProject")
if len(projects) != 1:
errors.append(f"Expected 1 IfcProject, found {len(projects)}")
return errors
# Check 2: All physical elements are spatially contained
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 spatially contained"
)
# Check 3: All IfcBuildingStorey are inside an IfcBuilding
for storey in model.by_type("IfcBuildingStorey"):
parent = ifcopenshell.util.element.get_aggregate(storey)
if parent is None or not parent.is_a("IfcBuilding"):
errors.append(
f"#{storey.id()} IfcBuildingStorey '{storey.Name}': "
f"Not aggregated in an IfcBuilding"
)
# Check 4: All IfcBuilding are inside an IfcSite
for building in model.by_type("IfcBuilding"):
parent = ifcopenshell.util.element.get_aggregate(building)
if parent is None or not parent.is_a("IfcSite"):
errors.append(
f"#{building.id()} IfcBuilding '{building.Name}': "
f"Not aggregated in an IfcSite"
)
return errors
errors = validate_spatial_structure("building.ifc")
if errors:
print(f"Spatial structure issues ({len(errors)}):")
for e in errors:
print(f" {e}")
else:
print("Spatial structure is valid")Example 12: Full QA Pipeline with Combined Reporting
# IfcOpenShell — all schema versions
import ifcopenshell
import ifcopenshell.validate
import ifcopenshell.util.element
import ifctester
import ifctester.reporter
import json
import sys
def run_full_qa_pipeline(ifc_path, ids_path=None, output_dir="."):
"""Complete QA pipeline: schema + IDS + custom checks."""
report = {
"file": ifc_path,
"schema_validation": {"status": "not_run", "issues": []},
"ids_validation": {"status": "not_run", "specs": []},
"custom_checks": {"status": "not_run", "issues": []},
"overall": "unknown"
}
model = ifcopenshell.open(ifc_path)
# --- Phase 1: Schema Validation ---
json_log = ifcopenshell.validate.json_logger()
ifcopenshell.validate.validate(model, json_log)
report["schema_validation"]["issues"] = [
{"level": s["level"], "message": s["message"]}
for s in json_log.statements
]
report["schema_validation"]["status"] = (
"pass" if not json_log.statements else "fail"
)
# --- Phase 2: IDS Validation ---
if ids_path:
ids = ifctester.open(ids_path)
ids.validate(model)
for spec in ids.specifications_:
report["ids_validation"]["specs"].append({
"name": spec.name,
"status": "pass" if spec.status else "fail",
"applicable": len(spec.applicable_entities_),
"passed": len(spec.passed_entities_),
"failed": len(spec.failed_entities_)
})
all_passed = all(s["status"] == "pass" for s in report["ids_validation"]["specs"])
report["ids_validation"]["status"] = "pass" if all_passed else "fail"
# Generate HTML report
html = ifctester.reporter.Html(ids)
html.report()
html.to_file(f"{output_dir}/ids_report.html")
# --- Phase 3: Custom Checks ---
custom_issues = []
# Check: All elements spatially contained
for element in model.by_type("IfcElement"):
container = ifcopenshell.util.element.get_container(element)
if container is None:
custom_issues.append(
f"#{element.id()} {element.is_a()}: No spatial container"
)
# Check: No duplicate GUIDs
guids = {}
for entity in model.by_type("IfcRoot"):
guid = entity.GlobalId
if guid in guids:
custom_issues.append(
f"Duplicate GUID '{guid}': #{guids[guid]} and #{entity.id()}"
)
guids[guid] = entity.id()
report["custom_checks"]["issues"] = custom_issues
report["custom_checks"]["status"] = "pass" if not custom_issues else "fail"
# --- Overall Status ---
statuses = [
report["schema_validation"]["status"],
report["ids_validation"]["status"],
report["custom_checks"]["status"],
]
report["overall"] = "pass" if all(s in ("pass", "not_run") for s in statuses) else "fail"
# Write JSON report
with open(f"{output_dir}/qa_report.json", "w") as f:
json.dump(report, f, indent=2)
print(f"QA Pipeline: {report['overall'].upper()}")
print(f" Schema: {report['schema_validation']['status']} ({len(report['schema_validation']['issues'])} issues)")
if ids_path:
print(f" IDS: {report['ids_validation']['status']} ({len(report['ids_validation']['specs'])} specs)")
print(f" Custom: {report['custom_checks']['status']} ({len(report['custom_checks']['issues'])} issues)")
return report
# Usage
report = run_full_qa_pipeline("building.ifc", "requirements.ids", "./qa_output")
if report["overall"] != "pass":
sys.exit(1)Example 13: Command-Line Validation
# Basic schema validation
python -m ifcopenshell.validate model.ifc
# Schema validation with EXPRESS WHERE rules
python -m ifcopenshell.validate model.ifc --rules
# Schema validation with JSON output
python -m ifcopenshell.validate model.ifc --json
# IDS validation with console output
python -m ifctester model.ifc requirements.ids
# IDS validation with HTML report
python -m ifctester model.ifc requirements.ids --reporter Html --output report.htmlValidation API Signatures
ifcopenshell.validate Module
validate()
ifcopenshell.validate.validate(
f, # ifcopenshell.file instance or str filepath
logger, # logging.Logger instance or json_logger instance
express_rules=False # bool — enable EXPRESS WHERE rule checking (10-100x slower)
) -> NoneValidates an IFC model against the EXPRESS schema definition. Checks entity attributes for type correctness, cardinality, enumerations, aggregations, GUID format, file header structure, and application references. Results are emitted via the logger, NOT returned.
Validation checks performed:
- Entity attribute type correctness (string, integer, float, entity reference)
- Inverse attribute cardinality (min/max counts of referencing entities)
- Simple type validation (IfcLabel, IfcLengthMeasure, etc.)
- Select type validation (value is a valid member of the select)
- Enumeration validation (value is a valid enum member)
- Aggregation validation (list/set size constraints)
- GUID format validation (22-character base64 format)
- File header structure validation
- Application reference validation
- EXPRESS WHERE rules (only when
express_rules=True)
---
json_logger
ifcopenshell.validate.json_logger() -> json_loggerReturns a logger instance that collects validation results as structured data.
Attributes:
statements—list[dict]— Collected validation log entriesstate—dict— Current contextual state
Methods:
log(level, message, *args)— Record a validation messageset_state(key, value)— Store contextual state (e.g., current entity)
Statement dict structure:
{
"level": str, # "WARNING", "ERROR", etc.
"message": str, # Formatted validation message
"instance": Any, # Entity instance reference (if applicable)
"attribute": str # Affected attribute name (if applicable)
}---
LogDetectionHandler
ifcopenshell.validate.LogDetectionHandler() -> LogDetectionHandlerA logging.Handler subclass that sets a flag when any message is logged.
Attributes:
message_logged—bool—Trueif any log record was emitted through this handler
Methods:
emit(record)— Process a log record (setsmessage_logged = True)
Usage: Add to a logging.Logger to detect if validate() produced any output.
---
validate_guid()
ifcopenshell.validate.validate_guid(
guid: str # 22-character IFC GlobalId string
) -> str | NoneReturns None if the GUID is valid (22-character base64). Returns an error description string if invalid.
---
validate_ifc_header()
ifcopenshell.validate.validate_ifc_header(
f, # ifcopenshell.file instance
logger # logging.Logger instance
) -> NoneValidates the IFC file header structure (FILE_DESCRIPTION, FILE_NAME, FILE_SCHEMA).
---
validate_ifc_applications()
ifcopenshell.validate.validate_ifc_applications(
f, # ifcopenshell.file instance
logger # logging.Logger instance
) -> NoneValidates application entity references in the IFC file.
---
assert_valid()
ifcopenshell.validate.assert_valid(
attr_type, # EXPRESS attribute type definition
val, # Attribute value to validate
schema, # Schema module (e.g., ifcopenshell.ifcopenshell_wrapper.schema_by_name("IFC4"))
no_throw=False, # bool — if True, return error instead of raising
attr=None # str — attribute name for error context
) -> None | strValidates a single attribute value against its EXPRESS schema type definition. When no_throw=False (default), raises ValidationError on failure. When no_throw=True, returns an error string or None.
---
assert_valid_inverse()
ifcopenshell.validate.assert_valid_inverse(
attr, # EXPRESS inverse attribute definition
val, # Inverse attribute value (set of entities)
schema # Schema module
) -> NoneValidates inverse attribute cardinality constraints. Raises ValidationError if the count of referencing entities violates the schema.
---
log_internal_cpp_errors()
ifcopenshell.validate.log_internal_cpp_errors(
f, # ifcopenshell.file instance
logger # logging.Logger instance
) -> NoneCaptures and logs errors from the C++ STEP parser that occurred during file loading.
---
ifctester Module
ifctester.open()
ifctester.open(
filepath: str, # Path to IDS XML file
validate: bool = False # Validate IDS XML against schema on load
) -> ifctester.ids.IdsLoads an IDS (Information Delivery Specification) file from disk.
---
ifctester.ids.from_string()
ifctester.ids.from_string(
xml: str, # IDS XML content as string
validate: bool = False # Validate against IDS XML schema
) -> ifctester.ids.IdsParses an IDS specification from an XML string.
---
ifctester.ids.Ids
ifctester.ids.Ids(
title: str | None = "Untitled",
copyright: str | None = None,
version: str | None = None,
description: str | None = None,
author: str | None = None,
date: str | None = None,
purpose: str | None = None,
milestone: str | None = None
)Key attributes:
specifications_—list[Specification]— List of validation specificationsinfo_—dict— Document metadatafilepath_—str— Source file pathfilename_—str— Source file name
Key methods:
| Method | Signature | Returns | Purpose |
|---|---|---|---|
validate | (ifc_file, should_filter_version=False, filepath=None) | None | Run all specifications against IFC file |
to_xml | (filepath="output.xml") | None | Write IDS to XML file |
to_string | () | str | Serialize IDS to XML string |
asdict | () | dict | Convert to dictionary |
parse | (data) | None | Parse IDS data into object |
---
ifctester.ids.Specification
ifctester.ids.Specification(
name: str = "Unnamed",
minOccurs: int = 0,
maxOccurs: int | str = "unbounded",
ifcVersion: list[str] | None = None,
identifier: str | None = None,
description: str | None = None,
instructions: str | None = None
)Key attributes:
applicability_—list— Facets defining which entities this spec applies torequirements_—list— Facets defining what data entities must haveapplicable_entities_—list— Entities matching applicability (populated after validate)passed_entities_—list— Entities passing all requirementsfailed_entities_—list— Entities failing requirementsstatus—bool | None— Overall pass/fail status
Key methods:
| Method | Signature | Returns | Purpose |
|---|---|---|---|
validate | (ifc_file, should_filter_version=False) | None | Validate this specification |
check_ifc_version | (ifc_file) | bool | Check IFC version compatibility |
get_usage | () | Cardinality | Get occurrence constraints |
set_usage | (usage) | None | Set required/optional/prohibited |
reset_status | () | None | Clear validation results |
---
IDS Facet Types
Entity Facet
ifctester.facet.Entity(
name: str, # IFC class name (e.g., "IfcWall")
predefinedType: str = None, # Optional predefined type filter
instructions: str = None # Human-readable instructions
)Attribute Facet
ifctester.facet.Attribute(
name: str, # Attribute name (e.g., "Name")
value: str = None, # Expected value or pattern
cardinality: str = "required", # "required", "optional", "prohibited"
instructions: str = None
)Classification Facet
ifctester.facet.Classification(
value: str = None, # Classification reference value
system: str = None, # Classification system name
uri: str = None, # Classification system URI
cardinality: str = "required",
instructions: str = None
)Property Facet
ifctester.facet.Property(
propertySet: str, # Property set name (e.g., "Pset_WallCommon")
baseName: str, # Property name (e.g., "FireRating")
value: str = None, # Expected value
dataType: str = None, # IFC data type (e.g., "IfcLabel")
uri: str = None, # Property set template URI
cardinality: str = "required",
instructions: str = None
)Material Facet
ifctester.facet.Material(
value: str = None, # Material name or pattern
uri: str = None, # Material URI
cardinality: str = "required",
instructions: str = None
)PartOf Facet
ifctester.facet.PartOf(
name: str = None, # Parent entity IFC class
predefinedType: str = None, # Parent predefined type
relation: str = None, # Relationship type (e.g., "IfcRelContainedInSpatialStructure")
cardinality: str = "required",
instructions: str = None
)All facets implement:
filter(ifc_file, elements) -> list— Filter/validate elements against facet criteria
---
IDS Reporter Classes
Console Reporter
ifctester.reporter.Console(
ids: ifctester.ids.Ids,
use_colour: bool = True
)Methods: report(), to_string(), write()
Json Reporter
ifctester.reporter.Json(
ids: ifctester.ids.Ids,
hide_skipped: bool = False
)Methods: report(), to_string(), to_file(filepath)
Html Reporter
ifctester.reporter.Html(
ids: ifctester.ids.Ids,
hide_skipped: bool = False
)Methods: report(), to_string(), to_file(filepath)
Bcf Reporter
ifctester.reporter.Bcf(
ids: ifctester.ids.Ids,
hide_skipped: bool = False
)Methods: report(), to_file(filepath)
Ods Reporter
ifctester.reporter.Ods(
ids: ifctester.ids.Ids,
excel_safe: bool = False
)Methods: report(), to_file(filepath)
Txt Reporter
ifctester.reporter.Txt(
ids: ifctester.ids.Ids
)Methods: report(), to_string(), to_file(filepath)
---
Georeference API Functions
add_georeferencing()
ifcopenshell.api.run("georeference.add_georeferencing", model,
ifc_class: str = "IfcMapConversion", # "IfcMapConversion" or "IfcMapConversionScaled" (IFC4X3 only)
name: str = "EPSG:3857" # Default CRS name
) -> NoneCreates empty IfcMapConversion (or IfcMapConversionScaled) and IfcProjectedCRS entities. MUST be called before edit_georeferencing.
---
edit_georeferencing()
ifcopenshell.api.run("georeference.edit_georeferencing", model,
coordinate_operation: dict[str, Any] | None = None,
projected_crs: dict[str, Any] | None = None
) -> Nonecoordinate_operation dict keys:
Eastings—float— False origin eastingNorthings—float— False origin northingOrthogonalHeight—float— Height above referenceXAxisAbscissa—float— cos(rotation angle) for Project NorthXAxisOrdinate—float— sin(rotation angle) for Project NorthScale—float— Scale factor (1.0 = no scaling)
projected_crs dict keys:
Name—str— EPSG code (e.g.,"EPSG:28992")Description—str— CRS descriptionGeodeticDatum—str— Datum nameVerticalDatum—str— Vertical datum nameMapProjection—str— Projection methodMapZone—str— Map zone identifierMapUnit— IFC unit reference (IFC4+) or string (IFC2X3)
---
edit_true_north()
ifcopenshell.api.run("georeference.edit_true_north", model,
true_north: tuple[float, float] | float | None = 0.0
) -> NoneAccepts a rotation angle in decimal degrees (anticlockwise from Y-axis positive) or a 2D direction vector (x, y).
---
edit_wcs()
ifcopenshell.api.run("georeference.edit_wcs", model,
x: float = 0.0, # WCS X offset
y: float = 0.0, # WCS Y offset
z: float = 0.0, # WCS Z offset
rotation: float = 0.0, # WCS rotation in radians
is_si: bool = True # True = meters, False = file native units
) -> NoneAdjusts the World Coordinate System origin. NEVER move WCS from (0,0,0) without a specific surveying reason.
---
remove_georeferencing()
ifcopenshell.api.run("georeference.remove_georeferencing", model) -> NoneRemoves all georeferencing data (IfcMapConversion, IfcProjectedCRS) from the model. In IFC2X3, removes the corresponding property sets.