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.
What happens when you pass a DWG
Section titled “What happens when you pass a DWG”- Convert. The server shells out to GNU LibreDWG’s
dwg2dxf. Exit status is ignored on purpose:dwg2dxfreturns nonzero for recoverable parse warnings, so the artifact is what gets trusted, not the return code. - Repair. The converter’s output is run through a newline-folding pass (see below) that fixes a specific way
dwg2dxfcorrupts text values. - 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.
- 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.
Reading the response
Section titled “Reading the response”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.
The repair pass
Section titled “The repair pass”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 FOR CULVERTS UNDERNEATH ROADWAY\P...7The 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.
Known limitation
Section titled “Known limitation”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.
Why there is no version fallback
Section titled “Why there is no version fallback”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.
What converts, and what does not
Section titled “What converts, and what does not”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)."}When a file is refused
Section titled “When a file is refused”ODA File Converter
Section titled “ODA File Converter”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:
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.
Save-as from the source application
Section titled “Save-as from the source application”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.
Rule of thumb
Section titled “Rule of thumb”| 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.
Availability
Section titled “Availability”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:
docker compose build --build-arg WITH_DWG=1Without 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 |
The FreeCAD path
Section titled “The FreeCAD path”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.
Public DWG files for testing
Section titled “Public DWG files for testing”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.