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.

What each export setting fixes for the consumer Four settings and the downstream consequence of each. The schema fixes where attributes live, the model view decides which elements exist at all, the property set mapping decides which parameters survive, and the coordinate base decides whether the model has a position in the world. None of them is visible in the resulting file. Schema release where every attribute lives IFC4 Model view which elements are present at all coordination Pset mapping which parameters become properties mapping file Coordinate base georeferenced, or at the origin shared coords

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.

The acceptance gate, in the order it fails cheapest Five checks run at the boundary before any downstream stage reads the file, ordered so the cheapest diagnosis comes first. The schema is one attribute read; element counts are a query; unit assignment and georeferencing are presence tests; the proxy ratio needs two counts. Each raises rather than warns, because a warning in a nightly log is a warning nobody read. Schema matches one attribute 1 a changed default is caught here Element count against a baseline 2 catches a truncated export Units declared presence test 3 geometry is unscaled without it Georeferenced map conversion present 4 a model fix, not an export fix Proxy ratio bounded two counts 5 the category mapping changed
# 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.

Why the proxy ratio is a better signal than the element count Two acceptance metrics compared on how they behave as a project develops. An element count drifts upward as design progresses, so its baseline needs constant maintenance and a genuine truncation hides inside normal growth. The proportion exported as generic proxies should not drift at all, so any movement in it is a change in the export rather than in the design. Element count — grows as the design develops — baseline needs maintaining — truncation hides inside growth — still worth checking Proxy ratio — stable across a project — no baseline maintenance — movement means the mapping changed — the stronger signal Check both; act on the ratio.

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.