Choosing ezdxf, pydwg, or ODA for Production CAD Pipelines

Choosing how to read CAD files reliably is the first decision in any pipeline that ingests Autodesk formats, and the answer depends far more on whether your inputs are DXF or DWG than on any single tool’s feature list. This guide compares three approaches within the Interoperability Decision Guides framework: pure-Python ezdxf, community DWG readers such as LibreDWG and the pydwg bindings, and the licensed ODA File Converter and Teigha/ODA SDK.

The decision matters because the three tools are not substitutes. ezdxf reads DXF perfectly and cannot open DWG at all; community readers open some DWG but carry coverage and stability risk; the ODA converter opens DWG reliably across the full version range but adds a licence and a binary dependency. Picking the wrong one does not fail at import — it fails weeks later when a production DWG lands on a headless runner that has no way to read it. As with every decision in the Interoperability Decision Guides section, the goal is to score the real options against coverage, fidelity, licensing, and operational fit before the choice is baked in.

CAD Reader Decision Tree A decision tree. The CAD source is tested for DWG. If it is not DWG it is read directly by ezdxf. If it is DWG, a second test asks whether headless CI operation and full version support are required; if yes the ODA File Converter produces DXF for ezdxf, if no a community reader such as LibreDWG or pydwg may be used for constrained experimental work. CAD source file .dwg or .dxf DWG? no — DXF ezdxf pure Python yes Headless CI + all versions? yes ODA Converter DWG to DXF to ezdxf no / experimental LibreDWG / pydwg partial, higher risk

Prerequisites

Before working through the decision, confirm your environment and assumptions:

  • Python 3.9+ — the snippets use pathlib, type hints, and f-strings. # python>=3.9
  • ezdxf ≥ 1.1.0 — install with pip install "ezdxf>=1.1.0". This is the reader for all DXF, whether the DXF was authored directly or produced by conversion from DWG.
  • ODA File Converter — a free-to-register desktop binary from the Open Design Alliance, needed only for the DWG route. On a server it requires a virtual display (xvfb). Confirm it is on PATH as ODAFileConverter.
  • xvfb (headless only) — apt-get install -y xvfb provides xvfb-run for driving the GUI converter without a display.
  • Knowledge of the DXF entity model — you should already be comfortable traversing model space and querying entities, as covered in the ezdxf Deep Dive. This guide is about reaching readable geometry, not about extracting it once you have it.
  • Awareness of DWG’s constraints — the reasons DWG cannot be read in pure Python are documented in pydwg Integration and the DWG Proprietary Limitations reference.

Architectural Overview

The three approaches differ along a small number of measurable dimensions. The table below scores each on the variables that actually decide production suitability.

Dimension ezdxf LibreDWG / pydwg ODA File Converter / Teigha SDK
Reads DXF Yes (R12–R2018) Via conversion only Writes DXF for ezdxf to read
Reads DWG No Partial, version-dependent Yes, reliably
DWG version range Subset, varies by release R12 through AutoCAD 2018 (AC1032) and later
Proxy object handling Exposes if present in DXF Often dropped Preserved through conversion (as proxies)
ACIS solid fidelity Raw payload exposed Frequently incomplete Preserved as ACIS in the DXF
Licensing Open source (MIT) Open source (GPL, LibreDWG) Free-to-register binary; SDK is commercial
Headless / CI Native, trivial Native (CLI/bindings) Needs xvfb; GUI-derived binary
Throughput High (pure parse) Moderate, variable Conversion adds a subprocess per file
Failure mode Clear exception on non-DXF Silent partial reads Non-zero exit / empty output

Two conclusions fall out of the table. First, ezdxf is not in competition with the DWG tools; it is the DXF reader that sits after whichever DWG strategy you pick. Second, the real contest is between community readers and the ODA converter, and it turns almost entirely on how much you value guaranteed version coverage and clear failure behaviour versus avoiding a licensed binary.

DWG version codes, for reference when validating what a converter must handle:

$ACADVER code AutoCAD release Notes
AC1009 R12 Oldest DWG the ODA converter targets as DXF output
AC1015 2000–2002 Widely present in legacy archives
AC1018 2004–2006 Common in municipal datasets
AC1021 2007–2009 Unicode layer names appear here
AC1024 2010–2012
AC1027 2013–2017
AC1032 2018+ Current production DWG version

Step-by-Step Implementation

1. Classify the source format

Never assume the extension is honest, but do start with it. A cheap suffix check routes the file; a magic-bytes check confirms it. DXF ASCII files begin with a 0\nSECTION group pair, and DWG files begin with an AC10xx version tag in their first six bytes.

# python>=3.9
from pathlib import Path


def classify_cad(path: Path) -> str:
    """Return 'dxf', 'dwg', or raise for anything unrecognised."""
    head = path.read_bytes()[:6]
    if head[:2] == b"AC" and head[2:6].isdigit():
        return "dwg"                      # e.g. b"AC1032"
    suffix = path.suffix.lower()
    if suffix == ".dxf":
        return "dxf"
    if suffix == ".dwg":
        return "dwg"
    raise ValueError(f"Not a recognised CAD source: {path.name}")

2. Read DXF directly with ezdxf

If the source is DXF, there is no decision left — ezdxf is the answer, and no conversion or licence is involved. Validate the version header before traversing so unsupported revisions fail early.

# ezdxf>=1.1.0 | python>=3.9
import ezdxf

SUPPORTED = {"AC1009", "AC1015", "AC1018", "AC1021", "AC1024", "AC1027", "AC1032"}


def read_dxf(path: str) -> ezdxf.document.Drawing:
    doc = ezdxf.readfile(path)
    ver = doc.header.get("$ACADVER", "UNKNOWN")
    if ver not in SUPPORTED:
        raise ValueError(f"Unsupported DXF revision: {ver}")
    return doc

3. Convert DWG with the ODA File Converter (production route)

If the source is DWG and the pipeline must run unattended across arbitrary versions, drive the ODA File Converter to produce DXF, then read that DXF with the function above. The converter is a GUI binary, so on a server it must be wrapped in xvfb-run. Its positional arguments are input directory, output directory, output version, output format, recurse flag, audit flag, and an optional filename filter.

# python>=3.9  (external: ODAFileConverter, xvfb-run)
import shutil
import subprocess
from pathlib import Path


def convert_dwg_to_dxf(dwg_path: Path, out_dir: Path, version: str = "ACAD2018") -> Path:
    """Convert a single DWG to DXF headlessly via the ODA File Converter."""
    if shutil.which("ODAFileConverter") is None:
        raise RuntimeError("ODAFileConverter not on PATH; DWG cannot be read.")
    out_dir.mkdir(parents=True, exist_ok=True)

    # args: in_dir out_dir out_version out_format recurse(0/1) audit(0/1) filter
    result = subprocess.run(
        [
            "xvfb-run", "-a", "ODAFileConverter",
            str(dwg_path.parent), str(out_dir),
            version, "DXF", "0", "1", dwg_path.name,
        ],
        capture_output=True,
        text=True,
        timeout=300,
    )
    dxf_path = out_dir / (dwg_path.stem + ".dxf")
    if result.returncode != 0 or not dxf_path.exists():
        raise RuntimeError(
            f"ODA conversion failed for {dwg_path.name}: "
            f"rc={result.returncode} stderr={result.stderr.strip()!r}"
        )
    return dxf_path

The audit flag (1) tells the converter to repair recoverable errors during conversion; leave it on for untrusted inputs. Because a hung GUI process would otherwise block a worker forever, the timeout is mandatory in production.

4. Compose the route

With classification, direct reading, and conversion in place, a single entry point implements the whole decision tree. This is the function the rest of a pipeline calls; it never has to know which strategy was used.

# ezdxf>=1.1.0 | python>=3.9
from pathlib import Path

import ezdxf


def open_cad(path: Path, work_dir: Path) -> ezdxf.document.Drawing:
    kind = classify_cad(path)
    if kind == "dxf":
        return read_dxf(str(path))
    # kind == "dwg": convert then read
    dxf_path = convert_dwg_to_dxf(path, work_dir / "_dxf")
    return read_dxf(str(dxf_path))

5. Assess fidelity before trusting the output

Reaching a readable document is necessary but not sufficient. Confirm the route preserved what the consumer needs. Proxy entities from vertical products and ACIS solids are the two things most often lost, so probe for them and log rather than assume.

# ezdxf>=1.1.0 | python>=3.9
import logging


def audit_fidelity(doc: ezdxf.document.Drawing) -> dict:
    msp = doc.modelspace()
    proxies = sum(1 for e in msp if e.dxftype() == "ACAD_PROXY_ENTITY")
    solids = sum(1 for e in msp if e.dxftype() == "3DSOLID")
    if proxies:
        logging.warning("%d proxy entities present; geometry may be opaque", proxies)
    return {"proxy_entities": proxies, "solids_3d": solids}

Edge Cases & Gotchas

DWG passed to ezdxf.readfile()

ezdxf.readfile() on a DWG raises, but the message is not always obvious to a caller who expected it to “just read CAD.” Classify first (Step 1) so the failure is a deliberate route decision, not an unhandled exception deep in a worker.

try:
    doc = ezdxf.readfile(str(path))
except (ezdxf.DXFStructureError, IOError):
    logging.error("Not readable as DXF; route through ODA if DWG: %s", path.name)
    raise

ODA converter silently produces no output

The converter can exit 0 yet write nothing when the target version is incompatible or the input is corrupt. Always assert the expected output file exists, as Step 3 does; never trust the return code alone.

Proxy objects survive conversion but stay opaque

The ODA converter preserves proxy entities as proxies — it does not decode the custom objects that Civil 3D or Plant 3D wrote. If the consumer needs that geometry, no reader here will supply it; the fix is upstream, by exploding proxies in the authoring application. This is the same constraint documented under DWG Proprietary Limitations.

Community readers report partial success

LibreDWG and pydwg may open a file and return a subset of entities without signalling that the rest were skipped. This silent partiality is the core production risk. If you use them, compare an entity count against a known-good ODA conversion of the same file before trusting the result, and confine them to input sets whose versions you have verified.

Encoding of layer names in older DWG

DWG written before the 2007 (AC1021) Unicode transition may carry code-page-encoded layer names that surface as mojibake after conversion. Normalise names on read and keep the original bytes in your audit log so a mis-decode is recoverable.

xvfb collisions under parallelism

Running many xvfb-run invocations concurrently can collide on display numbers. The -a flag selects a free display automatically; keep it, and give each worker its own output directory to avoid two conversions racing on the same DXF path.

Validation & Testing

Encode the decision as a test so it cannot silently regress when a library or the converter is upgraded. The test below asserts that both routes reach a readable document and that a DWG conversion yields at least as many entities as a stored baseline.

# ezdxf>=1.1.0 | python>=3.9
from pathlib import Path

import ezdxf


def test_dxf_route_reads_baseline():
    doc = ezdxf.readfile("tests/fixtures/plan_R2018.dxf")
    assert doc.header.get("$ACADVER") == "AC1032"
    assert sum(1 for _ in doc.modelspace()) >= 500


def test_dwg_route_matches_reference(tmp_path: Path):
    # convert_dwg_to_dxf / classify_cad imported from the pipeline module
    dxf_path = convert_dwg_to_dxf(Path("tests/fixtures/plan_R2018.dwg"), tmp_path)
    doc = ezdxf.readfile(str(dxf_path))
    count = sum(1 for _ in doc.modelspace())
    # Baseline captured from a known-good conversion; guards against silent loss.
    assert count >= 500, f"Conversion dropped entities: {count} < 500"

Keep one fixture per DWG version you support (R12 through AC1032) so a coverage regression in the converter or a reader shows up as a failing test rather than as missing geometry in production.

Performance & Scale

The DXF route is a pure parse and scales the way ezdxf does — bounded by entity count and I/O, comfortably thousands of entities per millisecond of iteration for typical drawings. The DWG route pays a fixed subprocess cost per file for conversion, which dominates for small files and amortises for large ones. Two levers matter at scale:

  • Batch conversions per converter invocation. The ODA converter accepts a directory and a recurse flag; converting a directory in one call amortises process startup across many files far better than one subprocess per file. Trade this against the coarser error granularity of a batch.
  • Separate the CPU-bound parse from the subprocess-bound conversion. Run conversions in a bounded pool sized to your CPU count and keep parsing in a separate stage, so a slow conversion does not starve parsing workers.

To choose a batch size, thread count, and regression threshold on evidence rather than guesswork, measure the parse stage directly. The benchmarking DXF parsing throughput in Python walkthrough gives a reusable harness for entities-per-second, megabytes-per-second, and peak memory, and shows how to turn those numbers into a CI regression gate.

FAQ

Can ezdxf read DWG files directly?

No. ezdxf reads and writes DXF only; it has no DWG parser. DWG is Autodesk’s closed binary format, and reading it requires either a community reverse-engineered library such as LibreDWG or a licensed converter such as the ODA File Converter, which produces DXF that ezdxf can then read. The pydwg Integration workflow covers the community-reader path in detail.

Is the ODA File Converter free to use in production?

The ODA File Converter is distributed free of charge under a registration and redistribution agreement, but it is not open source, and its licence terms govern redistribution. The underlying Teigha/ODA SDK, for embedding DWG support directly in your own application, is a paid commercial licence. Settle licensing at design time, not deployment, because it determines whether your workers can be licence-free containers.

Why not just use LibreDWG or pydwg for DWG in production?

Community DWG readers such as LibreDWG and the pydwg bindings cover only a subset of DWG versions and entity types, and coverage varies by release. They are viable for exploratory work or where inputs are constrained to known-good versions, but for unattended production across the full R12-to-2018 range the ODA converter is materially more reliable and, critically, fails loudly rather than returning partial reads.

How do I run the ODA File Converter on a headless server?

The ODA File Converter is a Qt GUI application, so it needs a display. On a headless server, wrap the invocation in xvfb-run -a to supply a virtual framebuffer, pass the input and output directories and the target DXF version as positional arguments, and check both the return code and that the expected output file exists. The converter writes DXF files that ezdxf then parses.