IFC Interoperability
RevitPy provides comprehensive IFC (Industry Foundation Classes) support through the revitpy.ifc module. Features include bidirectional element mapping, IFC export and import with configurable schema versions, IDS (Information Delivery Specification) validation, BCF (BIM Collaboration Format) issue management, and model diffing.
IFC functionality requires the optional ifcopenshell dependency. Install it with:
pip install revitpy[ifc]
Checking Availability
Before using IFC features, check whether ifcopenshell is installed:
from revitpy.ifc import ifc_available
if ifc_available():
print("IFC support is available")
else:
print("Install ifcopenshell: pip install revitpy[ifc]")
The ifc_available() function returns True when ifcopenshell is installed and importable.
IfcElementMapper
IfcElementMapper manages bidirectional mappings between RevitPy element type names and IFC entity type names. It ships with default mappings for common building elements and supports custom registration.
Default Mappings
The following mappings are registered automatically:
| RevitPy Type | IFC Entity Type |
|---|---|
WallElement |
IfcWall |
RoomElement |
IfcSpace |
DoorElement |
IfcDoor |
WindowElement |
IfcWindow |
SlabElement |
IfcSlab |
RoofElement |
IfcRoof |
ColumnElement |
IfcColumn |
BeamElement |
IfcBeam |
StairElement |
IfcStairFlight |
RailingElement |
IfcRailing |
Creating a Mapper
from revitpy.ifc import IfcElementMapper
mapper = IfcElementMapper()
# Inspect registered types
print(mapper.registered_types) # ["WallElement", "RoomElement", ...]
print(mapper.registered_ifc_types) # ["IfcWall", "IfcSpace", ...]
Looking Up Mappings
# RevitPy type -> IFC entity type
ifc_type = mapper.get_ifc_type("WallElement") # "IfcWall"
# IFC entity type -> RevitPy type
revitpy_type = mapper.get_revitpy_type("IfcDoor") # "DoorElement"
# Get the full IfcMapping dataclass
mapping = mapper.get_mapping("WallElement")
# IfcMapping(revitpy_type="WallElement", ifc_entity_type="IfcWall",
# property_map={}, bidirectional=True)
Registering Custom Mappings
Use register_mapping to add new type mappings or override existing ones. Property maps define how RevitPy attribute names translate to IFC property names.
mapper.register_mapping(
revitpy_type="CurtainWallElement",
ifc_entity_type="IfcCurtainWall",
property_map={"panel_count": "NumberOfPanels", "glazing_ratio": "GlazingAreaFraction"},
bidirectional=True,
)
# The mapping now works in both directions
assert mapper.get_ifc_type("CurtainWallElement") == "IfcCurtainWall"
assert mapper.get_revitpy_type("IfcCurtainWall") == "CurtainWallElement"
Set bidirectional=False when you only need the RevitPy-to-IFC direction (e.g. for export-only types):
mapper.register_mapping(
revitpy_type="AnnotationElement",
ifc_entity_type="IfcAnnotation",
bidirectional=False,
)
Converting Elements
The mapper can convert elements directly when ifcopenshell is available.
RevitPy to IFC:
# Convert a RevitPy element to an IFC entity inside an ifcopenshell file
ifc_entity = mapper.to_ifc(wall_element, ifc_file, config=export_config)
The to_ifc method creates an IFC entity in the given ifc_file, assigns a new GlobalId, copies the element name, and applies any property mappings from the registry.
IFC to dict:
# Convert an IFC entity to a dict representation
element_dict = mapper.from_ifc(ifc_entity)
# {"type": "WallElement", "ifc_type": "IfcWall", "global_id": "...", "name": "..."}
# With an explicit target type
element_dict = mapper.from_ifc(ifc_entity, target_type="WallElement")
IfcMapping Dataclass
| Field | Type | Default | Description |
|---|---|---|---|
revitpy_type |
str |
– | RevitPy element type name |
ifc_entity_type |
str |
– | IFC entity type name |
property_map |
dict[str, str] |
{} |
Maps RevitPy property names to IFC property names |
bidirectional |
bool |
True |
Whether the mapping works in both directions |
IfcExporter
IfcExporter converts collections of RevitPy elements into standards-compliant IFC files.
Export Configuration
Exports are configured through the IfcExportConfig dataclass:
| Field | Type | Default | Description |
|---|---|---|---|
version |
IfcVersion |
IfcVersion.IFC4 |
IFC schema version |
include_quantities |
bool |
True |
Include quantity data |
include_materials |
bool |
True |
Include material data |
site_name |
str |
"Default Site" |
Name for the IfcSite entity |
building_name |
str |
"Default Building" |
Name for the IfcBuilding entity |
author |
str |
"" |
Author metadata |
Basic Export
from revitpy.ifc import IfcExporter, IfcExportConfig, IfcVersion
# With defaults (IFC4)
exporter = IfcExporter()
output = exporter.export(elements, "model.ifc")
# With custom configuration
config = IfcExportConfig(
version=IfcVersion.IFC4,
include_quantities=True,
include_materials=True,
site_name="Project Site",
building_name="Main Building",
author="Design Team",
)
exporter = IfcExporter(config=config)
output = exporter.export(elements, "model.ifc")
Specifying Schema Version
The version parameter on the export method overrides the config default:
exporter = IfcExporter()
# IFC2X3 for legacy compatibility
exporter.export(elements, "model_2x3.ifc", version=IfcVersion.IFC2X3)
# IFC4 (default)
exporter.export(elements, "model_ifc4.ifc", version=IfcVersion.IFC4)
# IFC4X3 for latest standard
exporter.export(elements, "model_4x3.ifc", version=IfcVersion.IFC4X3)
IfcVersion Enum
| Value | String | Description |
|---|---|---|
IFC2X3 |
"IFC2X3" |
Legacy IFC 2x3 schema |
IFC4 |
"IFC4" |
IFC 4 schema (recommended) |
IFC4X3 |
"IFC4X3" |
IFC 4.3 schema (latest) |
Using a Custom Mapper
Supply a pre-configured IfcElementMapper to the exporter to use custom type mappings:
from revitpy.ifc import IfcExporter, IfcElementMapper
mapper = IfcElementMapper()
mapper.register_mapping("CurtainWallElement", "IfcCurtainWall")
exporter = IfcExporter(mapper=mapper)
exporter.export(elements, "model.ifc")
The exporter’s mapper is accessible via the mapper property:
exporter.mapper.register_mapping("PipeElement", "IfcPipeSegment")
Async Export
For non-blocking export with optional progress reporting:
import asyncio
from revitpy.ifc import IfcExporter
exporter = IfcExporter()
def on_progress(current: int, total: int) -> None:
print(f"Exporting {current}/{total}")
output = asyncio.run(
exporter.export_async(elements, "model.ifc", progress=on_progress)
)
IfcImporter
IfcImporter reads IFC files and converts their entities into RevitPy element dictionaries.
Import Configuration
Imports are configured through the IfcImportConfig dataclass:
| Field | Type | Default | Description |
|---|---|---|---|
merge_strategy |
str |
"replace" |
How to handle existing elements |
update_existing |
bool |
True |
Update elements that already exist |
create_new |
bool |
True |
Create elements that do not exist |
property_mapping |
dict[str, str] |
{} |
Rename properties during import |
Basic Import
from revitpy.ifc import IfcImporter
importer = IfcImporter()
elements = importer.import_file("model.ifc")
for elem in elements:
print(f"{elem['type']}: {elem['name']} (IFC: {elem['ifc_type']})")
Each imported element is a dict with at least these keys:
| Key | Description |
|---|---|
type |
RevitPy element type name |
ifc_type |
Original IFC entity type |
global_id |
IFC GlobalId |
name |
Element name |
Additional keys are added based on the mapper’s property map for that type.
Supported IFC Entity Types
The importer processes these entity types by default:
IfcWall,IfcWallStandardCaseIfcDoor,IfcWindowIfcSlab,IfcRoofIfcColumn,IfcBeamIfcStairFlight,IfcRailingIfcSpace
Unmapped entity types are skipped with a warning.
Custom Import Configuration
Use IfcImportConfig to rename properties during import:
from revitpy.ifc import IfcImporter, IfcImportConfig
config = IfcImportConfig(
merge_strategy="replace",
update_existing=True,
create_new=True,
property_mapping={"global_id": "revit_guid", "name": "element_name"},
)
importer = IfcImporter(config=config)
elements = importer.import_file("model.ifc")
# Elements now use "revit_guid" instead of "global_id", etc.
Async Import
import asyncio
from revitpy.ifc import IfcImporter
importer = IfcImporter()
elements = asyncio.run(importer.import_file_async("model.ifc"))
Round-Trip Workflow
Export elements to IFC and re-import them:
from revitpy.ifc import IfcExporter, IfcImporter, IfcElementMapper
# Share a mapper for consistent mappings
mapper = IfcElementMapper()
# Export
exporter = IfcExporter(mapper=mapper)
exporter.export(elements, "roundtrip.ifc")
# Import
importer = IfcImporter(mapper=mapper)
reimported = importer.import_file("roundtrip.ifc")
IDS Validation
IdsValidator checks elements against IDS (Information Delivery Specification) requirements. This does not require ifcopenshell – it works with any duck-typed element objects.
Defining Requirements
Requirements are defined using the IdsRequirement dataclass:
| Field | Type | Default | Description |
|---|---|---|---|
name |
str |
– | Requirement name |
description |
str |
"" |
Human-readable description |
entity_type |
str or None |
None |
Apply only to this element type (or all if None) |
property_name |
str or None |
None |
Property to check |
property_value |
str or None |
None |
Expected value (string comparison) |
required |
bool |
True |
Whether the property must exist |
Validating Elements Programmatically
from revitpy.ifc import IdsValidator, IdsRequirement
validator = IdsValidator()
requirements = [
IdsRequirement(
name="Wall fire rating",
description="All walls must have a fire rating",
entity_type="Walls",
property_name="fire_rating",
required=True,
),
IdsRequirement(
name="Door width check",
description="Doors must be at least 900mm wide",
entity_type="Doors",
property_name="width",
property_value="900",
required=True,
),
]
results = validator.validate(elements, requirements)
for result in results:
status = "PASS" if result.passed else "FAIL"
print(f"[{status}] {result.requirement.name}: {result.message}")
IdsValidationResult Fields
| Field | Type | Description |
|---|---|---|
requirement |
IdsRequirement |
The requirement that was checked |
passed |
bool |
Whether the check passed |
entity_id |
Any or None |
The element’s ID |
actual_value |
Any or None |
The actual property value found |
message |
str |
Human-readable result message |
Validating from a File
Requirements can be loaded from a JSON file. The file should contain a list of requirement objects:
[
{
"name": "Wall fire rating",
"description": "All walls must have a fire rating",
"entity_type": "Walls",
"property_name": "fire_rating",
"required": true
},
{
"name": "Room name required",
"description": "All rooms must have a name",
"entity_type": "Rooms",
"property_name": "name",
"required": true
}
]
from revitpy.ifc import IdsValidator
validator = IdsValidator()
results = validator.validate_from_file(elements, "requirements.json")
passed = sum(1 for r in results if r.passed)
total = len(results)
print(f"IDS validation: {passed}/{total} checks passed")
Validation Logic
The validator applies these rules for each element/requirement pair:
- If the requirement specifies an
entity_typethat does not match the element’s type, the check passes (not applicable). - If no
property_nameis specified, the check passes (no property to verify). - If
property_nameis set andrequiredisTrue, the property must exist on the element. - If
property_valueis also set, the actual value must match (string comparison).
BCF Issue Management
BcfManager provides simplified BCF 2.1 compatible issue tracking. It supports creating issues, serializing them to BCF ZIP archives, and reading them back.
Creating Issues
from revitpy.ifc import BcfManager
bcf = BcfManager()
issue = bcf.create_issue(
title="Missing fire rating",
description="Wall on Level 2 is missing its fire rating property",
author="John Smith",
status="Open",
assigned_to="Jane Doe",
element_ids=["wall-001", "wall-002"],
)
print(f"Created issue: {issue.title} ({issue.guid})")
BcfIssue Fields
| Field | Type | Default | Description |
|---|---|---|---|
guid |
str |
Auto-generated UUID | Unique issue identifier |
title |
str |
"" |
Short issue title |
description |
str |
"" |
Detailed description |
author |
str |
"" |
Author name |
creation_date |
datetime |
datetime.now() |
When the issue was created |
status |
str |
"Open" |
Issue status (e.g. "Open", "Closed") |
assigned_to |
str |
"" |
Person assigned to the issue |
element_ids |
list[str] |
[] |
Referenced element IDs |
Writing BCF Files
Write issues to a BCF ZIP archive:
# Write all managed issues
bcf.write_bcf(path="issues.bcf")
# Write a specific set of issues
bcf.write_bcf(issues=[issue], path="selected_issues.bcf")
The output is a ZIP archive containing an XML markup.xml file for each issue, organized by topic GUID.
Reading BCF Files
Read issues from BCF ZIP archives (.bcf, .bcfzip, .zip) or JSON files:
# Read from a BCF archive
issues = bcf.read_bcf("issues.bcf")
# Read from a JSON file
issues = bcf.read_bcf("issues.json")
for issue in issues:
print(f"[{issue.status}] {issue.title} (assigned to: {issue.assigned_to})")
Read issues are also added to the manager’s internal list, accessible via the issues property:
all_issues = bcf.issues
Full BCF Workflow
from revitpy.ifc import BcfManager
bcf = BcfManager()
# Create issues based on validation results
bcf.create_issue(
title="Missing fire rating on Level 2 walls",
description="3 walls on Level 2 do not have fire_rating set",
author="QA Script",
status="Open",
assigned_to="Structural Team",
element_ids=["wall-101", "wall-102", "wall-103"],
)
bcf.create_issue(
title="Door width non-compliant",
description="Door D-201 is below the minimum 900mm width",
author="QA Script",
status="Open",
assigned_to="Architecture Team",
element_ids=["door-201"],
)
# Export to BCF archive
bcf.write_bcf(path="qa_issues.bcf")
# Later, read them back
bcf2 = BcfManager()
issues = bcf2.read_bcf("qa_issues.bcf")
print(f"Loaded {len(issues)} issues")
IFC Diff
IfcDiff compares two sets of elements (or two IFC files) and produces a structured diff identifying added, modified, and removed entities.
Comparing Element Lists
Elements are matched by their id or global_id attribute. Property-level changes are tracked for modified elements.
from revitpy.ifc import IfcDiff
differ = IfcDiff()
result = differ.compare(old_elements, new_elements)
print(f"Added: {result.summary['added']}")
print(f"Modified: {result.summary['modified']}")
print(f"Removed: {result.summary['removed']}")
Comparing IFC Files
Compare two IFC files directly (requires ifcopenshell):
result = differ.compare_files("model_v1.ifc", "model_v2.ifc")
This imports both files via IfcImporter and then compares the resulting element lists.
IfcDiffResult Structure
| Field | Type | Description |
|---|---|---|
added |
list[IfcDiffEntry] |
Entities present only in the new set |
modified |
list[IfcDiffEntry] |
Entities that changed between old and new |
removed |
list[IfcDiffEntry] |
Entities present only in the old set |
summary |
dict[str, int] |
Counts: {"added": N, "modified": N, "removed": N} |
IfcDiffEntry Structure
| Field | Type | Description |
|---|---|---|
global_id |
str |
Element identifier |
entity_type |
str |
Element type name |
change_type |
IfcChangeType |
ADDED, MODIFIED, or REMOVED |
old_properties |
dict[str, Any] |
Properties from the old state |
new_properties |
dict[str, Any] |
Properties from the new state |
changed_fields |
list[str] |
Names of fields that differ (sorted) |
IfcChangeType Enum
| Value | String | Description |
|---|---|---|
ADDED |
"added" |
Entity exists only in the new state |
MODIFIED |
"modified" |
Entity exists in both states with property changes |
REMOVED |
"removed" |
Entity exists only in the old state |
Inspecting Changes
result = differ.compare(old_elements, new_elements)
# New elements
for entry in result.added:
print(f"+ {entry.entity_type} ({entry.global_id})")
# Removed elements
for entry in result.removed:
print(f"- {entry.entity_type} ({entry.global_id})")
# Modified elements with field-level detail
for entry in result.modified:
print(f"~ {entry.entity_type} ({entry.global_id})")
for field in entry.changed_fields:
old_val = entry.old_properties.get(field)
new_val = entry.new_properties.get(field)
print(f" {field}: {old_val} -> {new_val}")
Error Handling
The IFC module defines specific exceptions for each subsystem:
| Exception | Raised By | Description |
|---|---|---|
IfcError |
Base class | General IFC error |
IfcExportError |
IfcExporter.export, IfcElementMapper.to_ifc |
Export or conversion failure |
IfcImportError |
IfcImporter.import_file, IfcElementMapper.from_ifc |
Import or reverse-mapping failure |
IfcValidationError |
General validation | IFC validation failure |
IdsValidationError |
IdsValidator.validate_from_file |
IDS file parsing or validation failure |
BcfError |
BcfManager.read_bcf, BcfManager.write_bcf |
BCF file read/write failure |
All IFC operations that require ifcopenshell will raise ImportError with a message directing you to install it via pip install revitpy[ifc].
Combined Workflow Example
A complete workflow using IFC export, validation, diffing, and BCF issue tracking:
from revitpy.ifc import (
IfcExporter,
IfcImporter,
IfcElementMapper,
IfcExportConfig,
IfcVersion,
IdsValidator,
IdsRequirement,
BcfManager,
IfcDiff,
)
# Set up mapper with custom types
mapper = IfcElementMapper()
mapper.register_mapping("CurtainWallElement", "IfcCurtainWall")
# Export current model
config = IfcExportConfig(
version=IfcVersion.IFC4,
site_name="Main Campus",
building_name="Building A",
author="Design Team",
)
exporter = IfcExporter(mapper=mapper, config=config)
exporter.export(elements, "model_v2.ifc")
# Validate against IDS requirements
validator = IdsValidator()
requirements = [
IdsRequirement(
name="Fire rating required",
entity_type="Walls",
property_name="fire_rating",
required=True,
),
]
results = validator.validate(elements, requirements)
# Create BCF issues for failures
bcf = BcfManager()
for result in results:
if not result.passed:
bcf.create_issue(
title=f"IDS failure: {result.requirement.name}",
description=result.message,
author="Automated QA",
element_ids=[str(result.entity_id)] if result.entity_id else [],
)
if bcf.issues:
bcf.write_bcf(path="validation_issues.bcf")
# Compare against previous version
differ = IfcDiff()
diff_result = differ.compare_files("model_v1.ifc", "model_v2.ifc")
print(f"Changes: +{diff_result.summary['added']} "
f"~{diff_result.summary['modified']} "
f"-{diff_result.summary['removed']}")