An open-source Rust library that parses CAD files (DWG/DXF) into a model and exports that model as JSON, SVG or PNG. Read-only: it does not write DWG or DXF, and does not convert between them.
git submodule update --init # test fixtures only -- not needed to build (see "Platform")
cargo build --workspace
cargo test --workspacelet db = uncad::parse("drawing.dwg")?; // same model for DWG and DXF (entities + tables)
println!("{} entities", db.entities.len());
// The header variables that give the numbers a meaning come beside the model:
let (db, header) = uncad::parse_with_header("drawing.dxf")?;
println!("units: {:?}", header.units()); // $INSUNITS; None when the file does not say
// Bytes already in memory (from the network, an archive, ...):
let bytes = std::fs::read("drawing.dwg")?;
let db = uncad::parse_bytes(&bytes, uncad::Format::Dwg)?;
let json = db.to_json(uncad::ToJsonOptions { pretty: true })?; // the model, serialized as-is
std::fs::write("drawing.json", json)?;
// Rendering is the iron-render-cad crate's (uncad-cli uses it):
let result = iron_render_cad::to_svg(&db, iron_render_cad::ToSvgOptions::default());
std::fs::write("drawing.svg", result.svg)?;The binary is uncad. From a clone, cargo install --path crates/uncad-cli
puts it on your PATH; the lines below run it through cargo run instead.
cargo run -p uncad-cli -- drawing.dwg # summary: entity count per type
cargo run -p uncad-cli -- drawing.dwg -o drawing.json --pretty # export the parsed model
cargo run -p uncad-cli -- drawing.dwg -o drawing.svg # render to SVG (model space)
cargo run -p uncad-cli -- drawing.dwg -o drawing.png # render to PNG (via SVG)
cargo run -p uncad-cli -- drawing.dwg -o drawing.png --scale 2 # rasterize at twice the size
cargo run -p uncad-cli -- drawing.dwg -o drawing.png --fit 4000 # the longer side 4000 px
cargo run -p uncad-cli -- drawing.dwg -o drawing.png --fit 4000 --stroke 2 --paper dark # 2 px lines, black page
cargo run -p uncad-cli -- drawing.dwg -o detail.svg --window 0,0,500,300 # just this rectangle (drawing units)
cargo run -p uncad-cli -- drawing.dwg -o drawing.svg --no-trim # draw far-away outliers too
cargo run -p uncad-cli -- drawing.dwg -o sheet.svg --space paper # sheet borders / title blocks
cargo run -p uncad-cli -- drawing.dwg -o all.svg --space all # every space in one document
cargo run -p uncad-cli -- export drawing.dwg -o pkg/ # images + JSON an LLM or a vision model readsA render is drawn on a white page by default, where pure white (ACI 7) is drawn
black. --paper dark draws it on a black page instead: pure white stays white,
pure black is drawn white, and every other color is the file's -- the light ones
a white page washes out included. Lines are about 1/6000 of the picture's
diagonal unless --stroke <px> sets their width in pixels (PNG only).
Questions about a drawing, each answered as one line of JSON -- the result
structure of iron-scout-cad or
iron-diff-cad, serialized as it is:
uncad summarize drawing.dwg # what the drawing contains
uncad summarize drawing.dwg --type circle --layer pipes # ... and the circles on a layer, each with its box
uncad summarize drawing.dwg --id 812 --detail # ... and one entity's model record (the fields `set` takes)
uncad hit-test drawing.dwg --x 120 --y 45 --tolerance 0.5 # the entities at a point
uncad diff rev-a.dwg rev-b.dwg # exact numeric difference (by reference ID when the files show one drawing)
uncad diff a.dxf b.dxf --matching geometry --length-tolerance 0.01
uncad diff a.dxf b.dxf --matching geometry --min-similarity 1.5 # ... pairing only equal shapes
uncad diff rev-a.dwg rev-b.dwg --omit within,unstated # only what changed beyond toleranceA change set lists every field that differs, including those that moved within
tolerance and those one drawing does not state (a drawing saved again in a newer
format states values the older format had no place for). --omit leaves either
out -- for an agent reading the answer into a limited context -- and the answer
counts what it left out in omitted, so nothing left out reads as unchanged.
One verb edits: set changes one field of one entity and writes the result as a
new model JSON file -- never over an existing file, never into its input -- and
answers with the difference it made, as diff reports it. The field and the
value are named as the model JSON names them (the value is JSON: a number, a
string in quotes, an object). Every verb reads model JSON as a drawing, so edits
chain, and the last state can be compared with the first -- or rendered, since
every command but export (which reads the drawing's header) takes model JSON:
uncad set part.dwg --id 42 --path radius --value 6.5 -o step1.json
uncad set step1.json --id 42 --path center.x --value 10 -o step2.json
uncad diff part.dwg step2.json # both edits, and nothing else
uncad step2.json -o step2.svg # what the edited state looks likeredline draws that difference on top of the first drawing: the first drawing
exactly as it renders alone, and over it, in red (or --proposal-color), what
each changed entity became, what was removed (dashed), and a revision cloud
around every change. A change whose counterpart is uncertain gets a dashed
cloud and no geometry. When the drawing itself uses colors close to the one the
changes are drawn in, the answer lists them and a warning suggests another. It
writes an SVG or PNG file -- never over an existing one -- and answers with what
it marked and what it could not show, and why. --paper and --stroke mean what
they mean for a render; a stroke is in pixels of the picture, so it takes a PNG --
an SVG, which has none, refuses it:
uncad redline part.dwg step2.json -o proposal.svg
uncad redline part.dwg step2.json -o proposal.png --fit 2000 --omit within # 2000 px, beyond tolerance only
uncad redline plan.dwg step2.json -o detail.png --fit 1200 --frame changes # just the changes, and around them
uncad redline plan.dwg step2.json -o proposal.svg --proposal-color '#0057b8' # a drawing already in red
uncad redline plan.dwg step2.json -o review.png --fit 1200 --stroke 3 --paper dark # 3 px lines, black pageuncad mcp serves summarize, hit-test, diff, set and redline as Model Context
Protocol tools over stdio, for an agent to
call. A tool's answer is byte for byte what the command prints; a warning the
command writes to stderr is a further content block. Each call reads its files
again -- the server keeps no state -- and calls run one at a time.
{ "mcpServers": { "uncad": { "command": "uncad", "args": ["mcp"] } } }- DWG — read through LibreDWG
(GPLv3+), bound directly via Rust FFI (
bindgen). All versions. - DXF — ASCII and binary, R2007 and later included, read by the pure-Rust
undxfcrate (MIT) rather than by LibreDWG; chosen by file extension (parse) or by the caller (parse_bytes). A drawing's DXF and DWG read to the same model except where the two formats state different things (docs/CAVEATS.md, "DXF"). - Output — the parsed model as JSON (
CadDatabase::to_json, fromuncad-model). SVG and PNG come from theiron-render-cadcrate (MIT), whichuncad-cliuses. Writing DWG/DXF is not offered; the write API that existed in 0.1.0 was removed (seeCHANGELOG.md).
The model itself -- CadDatabase, Entity, Tables and their JSON form -- is
the uncad-model crate (MIT), re-exported
here as uncad::model / uncad::tables / uncad::json. This crate is one backend
that fills it; anything that only needs to read a drawing depends on the model
crate alone and inherits nothing from this crate's license.
Pure Rust plus native FFI. WebAssembly and the browser are not targets — the intended consumers are libraries and binaries (CLI, server, desktop app).
bindgen needs libclang, so LLVM/Clang has to be installed (Windows:
winget install LLVM.LLVM, Ubuntu: apt install libclang-dev). The LibreDWG C
sources are vendored into crates/libredwg-sys/vendor/libredwg/, so building
does not need the lib/libredwg submodule. Running cargo test --workspace
does: the real-file tests under crates/uncad/tests/ and uncad-cli's
tests/documented_invocations.rs read fixtures from that submodule's
test/test-data/. Clone with
git clone --recurse-submodules, or run git submodule update --init in an
existing clone. Builds and tests pass on Linux (x86_64-unknown-linux-gnu) as
well as Windows.
GPLv3-or-later. LibreDWG (GPLv3+) is the only third-party component linked
in, and its license carries over. The vendored copy carries local patches;
crates/libredwg-sys/NOTICE.md lists each one and is their modification
notice, inside the crate so that it reaches the published tarball. Copyright and license details for
third-party components are in
docs/THIRD_PARTY_NOTICES.md.
lib/libredwg/ LibreDWG upstream, as a git submodule. Not used by the build --
it is the source vendor/ is regenerated from, and where the
real-file test fixtures (test/test-data/) come from
crates/
libredwg-sys/ raw FFI (cc + bindgen). vendor/libredwg/ holds the subset of C
sources actually compiled (for publishing to crates.io), with
local patches marked "uncad local patch" and listed in
NOTICE.md; shim/ holds the C accessors for opaque types, and
vendor-config/config.h stands in for autotools
uncad/ the safe API: parse() / parse_bytes() -> uncad_model::CadDatabase,
and the drawing's Header beside it from parse_with_header()
uncad-cli/ the CLI binary (uncad)
crates/*/tests/ integration tests against the public API. crates/*/examples/ are
manual-check tools, and #[cfg(test)] blocks inside src/*.rs are
unit tests -- docs/ARCHITECTURE.md's "Test layout" says which
belongs where
scripts/ sync-libredwg-vendor.sh -- regenerates vendor/ after a submodule
update
samples/ gitignored except its README -- drop any DWG/DXF in here for
manual testing, no license clearance needed. No automated tests
read from it
docs/ architecture, known limitations, third-party notices
Further reading:
docs/ARCHITECTURE.md— crate layout, build system, test layout, the FFI/bindgen boundary, thread safety, the entity modeldocs/CAVEATS.md— entity type coverage, known limitations and bugs, cross-platform notes