Exporting Revit Models to IFC for Python Pipelines
An IFC export is the only route out of Revit that preserves typed products and property sets, and making it dependable for a Python pipeline is a configuration problem: pin the schema and model view, map every property the pipeline reads, export with shared coordinates, keep the configuration in version control, and accept the file only after automated checks. This page belongs to Revit and Navisworks Export Paths.
What the Exporter Decides on Your Behalf
The exporter makes four decisions that a downstream pipeline inherits, and all four are configurable.
The schema release fixes what entities exist and where attributes live. Code written against one release does not degrade gracefully against another; it stops matching. This is the same argument made for asserting the schema on read in IFC4x3 Schema Mapping, applied one stage earlier.
The model view definition determines which elements and representations are included. A coordination view and a reference view produce visibly different files from one model, and an element absent because of the view looks identical to an element that failed to export.
The property set mapping decides which parameters become properties. The default set covers the standard property sets and nothing else — every project-specific parameter needs naming.
The coordinate base decides whether the file carries georeferencing. Exporting on internal coordinates produces a model at the origin, which is geometrically intact and spatially meaningless.
Production-Ready Script
The export itself runs inside Revit; what a pipeline owns is acceptance. This script is the gate the export must pass before anything downstream reads it.
# ifcopenshell>=0.7.0, Python 3.9+
from __future__ import annotations
from dataclasses import dataclass, asdict
import ifcopenshell
@dataclass(frozen=True)
class ExportAcceptance:
schema: str
elements: int
proxies: int
has_units: bool
has_georeferencing: bool
missing_psets: tuple[str, ...]
@property
def proxy_ratio(self) -> float:
return self.proxies / self.elements if self.elements else 0.0
def accept_export(
path: str,
*,
expect_schema: str,
min_elements: int,
required_psets: tuple[str, ...] = (),
max_proxy_ratio: float = 0.05,
) -> ExportAcceptance:
"""Fail the export at the boundary, where the diagnosis is still cheap."""
model = ifcopenshell.open(path)
if model.schema != expect_schema:
raise ValueError(f"{path}: schema {model.schema}, expected {expect_schema}")
elements = model.by_type("IfcElement")
proxies = model.by_type("IfcBuildingElementProxy")
present: set[str] = set()
for rel in model.by_type("IfcRelDefinesByProperties"):
definition = rel.RelatingPropertyDefinition
name = getattr(definition, "Name", None)
if name:
present.add(name)
missing = tuple(p for p in required_psets if p not in present)
result = ExportAcceptance(
schema=model.schema,
elements=len(elements),
proxies=len(proxies),
has_units=bool(model.by_type("IfcUnitAssignment")),
has_georeferencing=bool(model.by_type("IfcMapConversion")),
missing_psets=missing,
)
if result.elements < min_elements:
raise ValueError(f"{path}: {result.elements} elements — export looks truncated")
if not result.has_units:
raise ValueError(f"{path}: no unit assignment — geometry is unscaled")
if not result.has_georeferencing:
raise ValueError(f"{path}: no map conversion — exported on internal coordinates")
if missing:
raise ValueError(f"{path}: property sets not exported: {', '.join(missing)}")
if result.proxy_ratio > max_proxy_ratio:
raise ValueError(
f"{path}: {result.proxy_ratio:.1%} of elements exported as proxies — "
"check the category mapping"
)
return result
if __name__ == "__main__":
print(asdict(accept_export(
"export.ifc", expect_schema="IFC4", min_elements=500,
required_psets=("Pset_WallCommon", "AssetRegister"),
)))
Key implementation notes:
- Every check raises rather than warns. A warning in a nightly log is a warning nobody read, and the whole value of the gate is that a bad export never reaches the pipeline.
- The required property sets are named by the pipeline, not by the exporter. That inverts the usual dependency: the consumer states what it needs and the export is measured against it.
- The proxy ratio is a stability signal. Element counts drift as a model develops; the proportion exported as generic proxies should not, so a change in it means the category mapping changed.
- Georeferencing is treated as required. Where a project genuinely has none, relax it deliberately with a comment rather than by omission.
Compatibility Matrix
| Component | Supported range | Notes |
|---|---|---|
ifcopenshell |
>=0.7.0 |
schema attribute and by_type |
| IFC schema | IFC2X3, IFC4, IFC4X3 | pin one; assert it here |
| Model view | coordination, reference | changes which elements are present |
| Exporter | any version | behaviour varies; version the configuration |
| Coordinate base | shared coordinates | required for the georeferencing check |
Fallback Strategies
1. Schema mismatch after an exporter upgrade. The default changed. Pin the schema in the configuration file rather than relying on the dialog default, and let this check catch the drift.
2. Missing property sets. The mapping file was not applied, or the parameter name changed. The check names exactly which sets are absent, which turns a downstream None into an export-side fix.
3. High proxy ratio. Categories with no natural IFC class. Extend the category mapping where a real class exists, and where none does, accept the proxies deliberately by raising the threshold with a note.
4. No map conversion. The model has no survey point or specified coordinate base. This is a model fix, not an export fix; route it to whoever owns the model rather than working around it downstream.
5. Element count far below baseline. Usually a view or phase filter, or linked models excluded from the export scope. Compare against the previous accepted export rather than against an absolute number, since a model legitimately grows.
FAQ
Which IFC schema should I export?
Whichever your consuming code is written against, pinned. IFC4 is the common production target and IFC2X3 remains widespread in coordination workflows; IFC4X3 matters when infrastructure entities such as alignments are in scope. The wrong answer is “whatever the exporter defaults to”, because that changes with the exporter version and takes your attribute paths with it.
Why are my shared parameters missing from the export?
Because parameters are not property sets. Revit exports a standard set of property sets by default, and anything else — shared parameters, project parameters, family parameters — needs an explicit mapping file that names the parameter and the property set it should be written into. Without it the geometry arrives complete and the field the pipeline reads is absent.
How do I know the export was georeferenced?
Check for a map conversion in the output rather than checking the export dialog. If the model has no defined survey point and specified coordinate base there is nothing to export, and no exporter setting will invent one — the fix is in the model. The acceptance check below fails the export rather than letting an unreferenced model into the pipeline.
Related Pages
- Revit and Navisworks Export Paths — parent reference comparing the three export routes
- ifcopenshell Workflow — reading the export this guide produces
- Reading IFC Georeferencing with ifcopenshell — confirming the shared coordinates actually made it into the file