uncad
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.
Quick start
let db = parse?; // same model for DWG and DXF (entities + tables)
println!;
// The header variables that give the numbers a meaning come beside the model:
let = parse_with_header?;
println!; // $INSUNITS; None when the file does not say
// Bytes already in memory (from the network, an archive, ...):
let bytes = read?;
let db = parse_bytes?;
let json = db.to_json?; // the model, serialized as-is
write?;
// Rendering is the iron-render-cad crate's (uncad-cli uses it):
let result = to_svg;
write?;
CLI
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.
A 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:
A 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:
redline 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 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.
Scope
- 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.
Platform
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.
License
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.
Repository layout
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