Skip to content

Handle DWG files

Hand a tool a .dwg and it works. Every tool in the pure-ezdxf tier (survey_dxf_layers, extract_parcels, extract_annotations, list_sheets, measure_by_layer, render_svg) plus the FreeCAD-backed open_cad_document accepts DWG directly, converting it behind the scenes. There is no pre-conversion step to remember and no separate DXF to keep in sync.

That convenience rests on a converter with real, documented rough edges, so this page is mostly about what the pipeline does to a DWG on its way in, and how to read the result honestly.

  1. Convert. The server shells out to GNU LibreDWG’s dwg2dxf. Exit status is ignored on purpose: dwg2dxf returns nonzero for recoverable parse warnings, so the artifact is what gets trusted, not the return code.
  2. Repair. The converter’s output is run through a newline-folding pass (see below) that fixes a specific way dwg2dxf corrupts text values.
  3. Validate. The repaired DXF is parsed with ezdxf’s recover mode and its entities are counted across modelspace, every paperspace layout, and every block definition. A file that will not parse is refused with an actionable message rather than handed onward.
  4. Cache. The validated DXF is written under TEMP_DIR/dwg-cache. The cache key is path plus size plus mtime, which is instant even on a 22 MB file and invalidates the moment the source is edited. Repeat calls on the same drawing reuse it.

Nothing about the DXF path changes. A .dxf input skips all four steps.

Results derived from a converted DWG carry a converted_from_dwg object, so the reported source is never silently a cache artifact:

{
"success": true,
"parcels_found": 38,
"converted_from_dwg": {
"source_dwg": "site-plan.dwg",
"converter": "dwg2dxf",
"cached": false,
"newline_repairs": 11
}
}

newline_repairs is worth glancing at. A nonzero count means the converter damaged text values and the repair pass put them back. A suspicious zero on a text-heavy drawing is the signal described under known limitation.

This is the interesting part, and it is a genuine converter bug rather than a formatting preference.

DXF is a strict alternation of lines: a group code, then its value, then the next group code, and so on. dwg2dxf can emit a literal newline inside a group-code-1 (text) value, usually where its output buffer happens to end. That single stray newline desynchronizes the whole alternation, and from that point on no reader can make sense of the file. Lenient parsing does not help, because recover mode fixes malformed entities, not a file whose line structure has shifted by one.

Observed in the wild, mid-word, on a real plan set:

1
...12" OF MINIMUM COVER FO
R CULVERTS UNDERNEATH ROADWAY\P...
7

The fix depends on a fact about DXF itself: genuine MTEXT line breaks are encoded as the \P escape, visible in that same value. So a literal newline inside a value is never intent. It is always damage. The repair therefore folds the continuation back onto the previous line with no separator at all, restoring FOR instead of corrupting it into FO R.

On the 22 MB civil plan set used as the reference fixture, this fires 11 times. Without it, that file is unreadable by anything.

The pass decides “is this line a group code?” by checking whether it parses as an integer in the valid DXF range of 0 to 1071. If a continuation fragment happens to be a bare integer in that range, it is indistinguishable from a real group code and that one fold is skipped. Text values that split mid-token are the overwhelmingly common case, so this is rare, and the newline_repairs count is returned precisely so a caller can notice a zero that looks wrong for the file in hand.

dwg2dxf --as rNNNN can retarget the output to an older DXF version, and it is tempting: on files libredwg mangles, downgrading to r14 or r2000 really does produce a DXF that parses cleanly.

It also produces a DXF containing zero entities. Not fewer entities, none, measured across modelspace, blocks, and paperspace alike. The parse succeeds because the file is structurally valid; there is simply nothing in it.

A silently empty drawing is a much worse failure than an error, because every downstream answer stays plausible. Zero parcels reads like a drawing without parcels. So the fallback is deliberately absent, and conversion validates its own output instead: parse it, count entities everywhere they can hide, and refuse a hollow result with a message that says what to do next.

Measured with libredwg 0.13.3 against a four-file public corpus spanning Release 10 (1988) through 2018, plus the two working fixtures:

File Format Result
Public 2018 sample (LibreDWG test suite) AC1032 Converts cleanly
Municipal 2013 standard detail AC1027 Converts cleanly
Vendor equipment drawing, 2000-era mechanical AC1015 Converts cleanly
22 MB civil plan set AC1032 Converts cleanly, 11 newline repairs
Vendor 2007 blocks sample AC1021 Refused — converted output is unparseable
Release 10 engineering sheet AC1006 Refused — converted output is unparseable

The honest summary: modern vintages work well, some older and odd files do not, and the failure is always explicit. Two of six is a real refusal rate, and a pipeline that reads DWG from arbitrary sources should expect to handle it.

A refused file returns:

{
"error": "libredwg could not produce a parseable DXF for site-plan.dwg: DXFStructureError: .... Convert this file with another tool (ODA File Converter, or the authoring app's own DXF export)."
}

The Open Design Alliance’s free (closed-source, registration-gated) batch converter is the fidelity benchmark. It handles every DWG version including current releases, converts round-trippably, and is what commercial tools embed. Command-line batch mode:

Terminal window
ODAFileConverter /input /output ACAD2018 DXF 0 1 "*.dwg"

Trade-offs: closed source, a manual download that cannot live in your Dockerfile without licensing care, and GUI-toolkit dependencies that make slim containers awkward. That combination is exactly why libredwg is the built-in path and ODA is the escape hatch.

Feed the resulting DXF straight to the tools. Either flavor is fine: the analysis tools accept ASCII and binary DXF alike (the loader sniffs the encoding), so whichever encoding your converter emits works without a second conversion step.

If you can ask the file’s author for anything, ask for a DXF, or better, an “exploded” AutoCAD-flavor export. This sidesteps conversion entirely and, for Civil 3D files, is the only path that recovers proxy-object graphics. Conversion does not change that situation at all: see The Civil 3D interchange trap for why no converter, built in or otherwise, can help with that class of file.

Situation Do this
Any DWG, first attempt Just pass it, the server converts on demand
Conversion refused the file ODA File Converter, then pass the DXF
You can reach the author Request a DXF export from the source application
Civil 3D file with proxy objects Only a source-application export helps, read this first

Whatever route produced the DXF, a layer survey is the cheap sanity check: run survey_dxf_layers and compare entity counts against the file size. Implausibly low counts mean content was lost somewhere upstream.

DWG conversion needs the dwg2dxf binary. It is picked up automatically from PATH, so a host or stdio deployment with libredwg installed needs no configuration.

Debian does not package libredwg, so the Docker image builds it from source and only when asked:

Terminal window
docker compose build --build-arg WITH_DWG=1

Without that argument, DWG input returns a clean, actionable error naming the missing binary, and every DXF path is unaffected. Two environment variables tune the rest (see Configuration):

Variable Default Purpose
DWG2DXF_PATH dwg2dxf Override the binary location
DWG_CONVERT_TIMEOUT 300 Seconds before a conversion is abandoned

FreeCAD’s own DWG support depends on an external converter being configured, which in practice means the ODA File Converter and its licensing paperwork. The server routes around that: DWG passed to open_cad_document (and the other FreeCAD tools that open a file) goes through the same libredwg conversion, and FreeCAD receives the resulting DXF through the Draft importer. So DWG support in the FreeCAD tier follows the same capability matrix as above, and needs FreeCAD and dwg2dxf present.

Exercising a conversion pipeline needs real DWG input, and real DWG files you’re actually allowed to use are surprisingly hard to come by. These sources are all verified by download:

Source What you get License / access
GNU LibreDWG test suite (test/test-data/) The same example drawing saved across six format generations (r13 → 2018), a tiny 20 KB sample_2018.dwg, and per-entity micro-fixtures. The best cross-version corpus available. GPL
USDA Forest Service T&D drawing library 673 real engineering sheets, including genuinely ancient Release 10 (AC1006) files that stress old-format handling. US public domain
Autodesk official samples Blocks, tables, and annotation-scaling samples in 2007/2010 formats. Freely downloadable but not explicitly redistributable — fetch at test time rather than vendoring
Municipal standard-detail libraries Modern real-world sheets — e.g. the City of Raleigh publishes hundreds of DWG standard details on a public blob store. Public records

A corpus assembled from these is what the capability matrix above was measured against, and it is worth reproducing: the Release 10 and 2007 samples are the ones that fail, so a test set drawn only from modern sources will make libredwg look better than it is.

Quick identification trick: the first six bytes of a DWG are an ASCII version tag. head -c 6 file.dwg reads it directly — AC1006 is Release 10, AC1009 R11/R12, AC1015 2000, AC1018 2004, AC1021 2007, AC1024 2010, AC1027 2013, AC1032 2018. Handy for sorting a downloaded pile by format generation before pointing a converter at it.