Detecting and Routing DWG Version Compatibility in Python Pipelines

Detecting DWG version compatibility requires mapping the 6-byte ACADxxxx binary header at file offset 0x00 to a schema revision, then routing the file through a version-aware converter before any GIS or BIM ingestion stage runs. The .dwg extension alone carries no schema guarantee — each major AutoCAD release introduces new compression algorithms, object-ID widths, or cloud metadata blocks that silently corrupt naive parsers. As part of the DWG Proprietary Limitations workflow, header-first routing is the minimal safeguard that keeps interoperability pipelines deterministic.


How the 6-Byte Header Controls Parsing

Every DWG file stores a 6-byte ASCII version string starting at file offset 0x00. This code is the sole reliable signal of the binary schema in use — no reliable fallback exists once parsing has started. Autodesk introduced LZ77 section compression at AC1018 (2004), widened object IDs from 32-bit to 64-bit at AC1027 (2013), and embedded cloud-sync metadata blocks at AC1032 (2018). Each change breaks parsers that were not written against that schema.

Because the DWG specification is proprietary, reverse-engineered parsers — including the Open Design Alliance (ODA) libraries — sometimes fail silently on unsupported codes rather than raising exceptions. This means the failure mode you must guard against is not a crash but a silently truncated geometry set that passes downstream validation while missing entire layer groups or 3D solids. The Core Format Fundamentals & Schema Mapping layer in your pipeline is the right place to gate files by version before any geometry or attribute extraction runs.

DWG Version Routing Pipeline Four-stage pipeline diagram showing: 1) Read 6-byte header from DWG file, 2) Lookup ACAD code in version registry, 3) Route to ODA converter with target version, 4) Normalised DXF output for GIS/BIM ingestion. DWG File Read 6 bytes offset 0x00 Version Registry ACAD code lookup → export target ODA Converter odafileconverter DWG → DXF Normalised DXF proxy objects stripped GIS / BIM ingestion unknown code → fall back to AC1032 Stage 1 Stage 2 Stage 3 Stage 4

ACAD Version Registry

The table below maps every ACAD code encountered in production to its AutoCAD release year, a routing compatibility tier, and the architectural changes that affect parser behaviour.

ACAD Code Release Routing Tier Key Schema Changes
AC1009 R11 / R12 Legacy — universal Early binary/ASCII hybrid; all ODA versions accept this
AC1012 R13 Stable First fully binary DWG; baseline for legacy GIS ingestion
AC1014 R14 Stable Standardised entity dictionaries; widely interoperable
AC1015 2000 High Object Enablers and proxy objects introduced
AC1018 2004 High LZ77 section compression; 3D solid kernel update
AC1021 2007 Moderate ACIS 7.0 kernel; extended XREF handling
AC1024 2010 Moderate Dynamic blocks; parametric constraints added
AC1027 2013 Low 64-bit object IDs; new hash-table structures
AC1032 2018–2026 Low PDF underlay enhancement; cloud-sync metadata; current schema — AutoCAD 2019–2026 all use R2018
Unknown Reject / warn No public DWG schema beyond AC1032 has shipped as of mid-2026; treat unrecognised codes as unconfirmed and apply the AC1032 fallback

Files whose ACAD code exceeds your converter’s maximum supported version will either raise a parsing exception or — more dangerously — silently drop entities. The three-stage routing strategy is:

  1. Header inspection: read the first 6 bytes synchronously before loading the full binary into memory.
  2. Code lookup: match the header against the registry; emit a structured warning and apply the fallback version for any code not in the map.
  3. Authorised conversion: downgrade to a stable baseline (AC1015 or AC1018 for most GIS consumers) using the ODA File Converter CLI — see the Open Design Alliance File Converter documentation for supported flags.

Production-Ready Script

The module below detects the DWG version, resolves the export target from the registry, calls odafileconverter, and enforces all four fallback paths. It requires Python 3.9+ and odafileconverter on $PATH.

# dwg_version_router.py
# Requires: Python >= 3.9, odafileconverter installed from Open Design Alliance
import os
import subprocess
import logging
from pathlib import Path
from typing import Optional

logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
logger = logging.getLogger(__name__)

# Maps 6-byte ACAD header bytes to the ODA File Converter target-version string.
# AC1032 is the current DWG schema; AutoCAD 2019–2026 all write this version.
VERSION_MAP: dict[bytes, str] = {
    b"AC1009": "R12",
    b"AC1012": "R13",
    b"AC1014": "R14",
    b"AC1015": "2000",
    b"AC1018": "2004",
    b"AC1021": "2007",
    b"AC1024": "2010",
    b"AC1027": "2013",
    b"AC1032": "2018",
}

# Fallback target for any unrecognised ACAD code (e.g. future releases).
FALLBACK_VERSION = "2018"

# Minimum plausible DWG file size; rejects stubs and zero-byte placeholders.
MIN_DWG_BYTES = 1024


def read_dwg_version(file_path: Path) -> Optional[bytes]:
    """
    Extract the 6-byte ACAD version header from a DWG file.

    Returns None on I/O error or if the file is smaller than MIN_DWG_BYTES.
    Does NOT raise — callers must treat None as a hard rejection signal.
    """
    if not file_path.exists():
        logger.error("File not found: %s", file_path)
        return None
    if file_path.stat().st_size < MIN_DWG_BYTES:
        logger.error(
            "File too small (%d bytes); rejecting before subprocess invocation: %s",
            file_path.stat().st_size,
            file_path,
        )
        return None
    try:
        with open(file_path, "rb") as fh:
            header = fh.read(6)
        if not header.startswith(b"AC"):
            logger.error(
                "Missing ACAD prefix in first 6 bytes of %s — not a DWG file",
                file_path,
            )
            return None
        return header
    except OSError as exc:
        logger.error("Failed to read header from %s: %s", file_path, exc)
        return None


def convert_dwg_to_dxf(
    input_path: Path,
    output_dir: Path,
    target_version: str = FALLBACK_VERSION,
    timeout_seconds: int = 120,
) -> Path:
    """
    Convert a DWG file to DXF at the given target version using odafileconverter.

    Raises RuntimeError on non-zero exit, FileNotFoundError if the CLI is absent,
    and TimeoutExpired if conversion exceeds timeout_seconds.
    """
    output_dir.mkdir(parents=True, exist_ok=True)
    safe_version = target_version.replace(" ", "")
    output_path = output_dir / f"{input_path.stem}_v{safe_version}.dxf"

    cmd = [
        "odafileconverter",
        str(input_path.parent),  # ODA takes an input directory
        str(output_dir),
        "DXF",
        target_version,
        "0",   # recurse flag (0 = no recursion)
        "1",   # audit flag (1 = audit on read)
    ]
    logger.info(
        "Converting %s → DXF (target schema: %s)", input_path.name, target_version
    )
    try:
        result = subprocess.run(
            cmd,
            check=True,
            capture_output=True,
            text=True,
            timeout=timeout_seconds,
        )
        if result.stdout:
            logger.debug("odafileconverter stdout: %s", result.stdout.strip())
        logger.info("Conversion complete: %s", output_path)
        return output_path
    except subprocess.CalledProcessError as exc:
        logger.error(
            "odafileconverter exited %d: %s", exc.returncode, exc.stderr.strip()
        )
        raise RuntimeError(
            f"ODA converter failed with exit code {exc.returncode}"
        ) from exc
    except FileNotFoundError:
        logger.error(
            "odafileconverter not found in PATH — install from Open Design Alliance"
        )
        raise
    except subprocess.TimeoutExpired:
        logger.error("Conversion timed out after %ds for %s", timeout_seconds, input_path)
        raise


def process_dwg(file_path: Path, output_dir: Path) -> Path:
    """
    End-to-end DWG version detection and DXF conversion pipeline.

    Returns the Path to the converted DXF file ready for GIS or BIM ingestion.
    """
    header = read_dwg_version(file_path)
    if header is None:
        raise ValueError(f"Cannot process {file_path}: invalid or unreadable DWG")

    if header not in VERSION_MAP:
        logger.warning(
            "Unrecognised ACAD code %r — applying fallback target %s",
            header.decode("ascii", errors="replace"),
            FALLBACK_VERSION,
        )
        target = FALLBACK_VERSION
    else:
        target = VERSION_MAP[header]
        logger.info(
            "Detected %s → AutoCAD %s",
            header.decode("ascii"),
            target,
        )

    return convert_dwg_to_dxf(file_path, output_dir, target)


# ---------------------------------------------------------------------------
# CLI usage example
# ---------------------------------------------------------------------------
if __name__ == "__main__":
    import sys

    if len(sys.argv) != 3:
        print("Usage: python dwg_version_router.py <input.dwg> <output_dir>")
        sys.exit(1)

    dxf_out = process_dwg(Path(sys.argv[1]), Path(sys.argv[2]))
    print(f"Output: {dxf_out}")

Key implementation notes:

  • odafileconverter takes an input directory, not an individual file path. The script passes input_path.parent so only the target file is in scope; isolate files in a temporary directory if you process batches to avoid converting siblings.
  • The audit flag (1) instructs ODA to run an internal consistency check on read, which surfaces corrupted entity references that would otherwise produce silent geometry gaps.
  • capture_output=True prevents ODA’s verbose progress output from polluting the calling process’s stdout while still making stderr available for structured error logging.
  • Cache converted DXF outputs by hashing input_path + target_version to avoid redundant conversions in high-throughput pipelines. A simple {hash}.dxf naming convention in a shared cache directory is sufficient.

Compatibility Matrix

Component Supported Range Notes
Python 3.9 – 3.13 Uses dict[bytes, str] PEP 585 syntax; requires 3.9+
odafileconverter ODA 25.x – 26.x Free download from Open Design Alliance; no Python binding — subprocess only
Input DWG schema AC1009AC1032 AC1032 is the current ceiling; codes beyond it are treated as unknown
Output DXF target R12 – 2018 R2018 (AC1032) is the recommended target for ezdxf and most GIS consumers
Operating system Linux, Windows, macOS odafileconverter ships as a native binary per platform; PATH setup differs
ezdxf downstream ezdxf >= 1.1.0 Use ezdxf to parse the converted DXF for geometry extraction and layer filtering

Fallback Strategies and Troubleshooting

1. FileNotFoundError: odafileconverter The ODA binary is not on $PATH. On Linux, install the .run bundle from the Open Design Alliance and add the install directory to ~/.bashrc. On Docker-based pipelines, bake the binary into the image and verify with which odafileconverter at container startup.

2. Unrecognised ACAD code in the registry If read_dwg_version returns a header not in VERSION_MAP (e.g. a future AC1035), the pipeline logs a warning and attempts AC1032 as the conversion target. If ODA also rejects that code, the only option is a read-only metadata pass: log the raw header bytes, the file size, and any layer names extractable from the ASCII sections of the DWG header, then quarantine the file for manual review.

3. Silent entity loss after conversion Compare the layer count in the input DWG (readable via odafileconverter audit output or a lightweight binary scan for the LAYER section marker) against the layer count in the converted DXF parsed with ezdxf. A mismatch signals proxy objects that were silently dropped. Re-run with a lower target version (AC1015) to see if ODA can reconstruct the geometry without the proprietary extensions.

4. RuntimeError: ODA converter failed with exit code 1 ODA exit code 1 usually indicates an encrypted or password-protected DWG. There is no programmatic bypass — request an unprotected export from the source. Log the file hash and notify the upstream data provider.

5. Conversion timeout on large DWG files Increase timeout_seconds in convert_dwg_to_dxf. For files above 500 MB, pre-split the DWG into model-space and paper-space portions using an authorised tool before invoking the converter; monolithic files with thousands of xrefs frequently exceed 120-second limits on commodity hardware.