Skip to content
Conduit

mcp server

uncad

Rust library for parsing, rendering (SVG/PNG), and writing CAD DWG/DXF files — safe FFI bindings over LibreDWG.

iyulab/uncadcommunitylistedunreviewed

Set up

Runs on your computer. Pick your agent and operating system.

This project does not publish an install method we can turn into a config. Follow the README instead.

README

synced Oct 26, 2026

uncad

CI License: GPL v3

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

git submodule update --init   # test fixtures only -- not needed to build (see "Platform")
cargo build --workspace
cargo test --workspace
let 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)?;

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.

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 reads

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: pixels of the image in a PNG, pixels on the screen at any zoom in an SVG (a style rule with vector-effect: non-scaling-stroke).

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 relate drawing.dwg                                      # what each dimension's definition points sit on
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 tolerance

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:

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 like

redline draws that difference on top of the first drawing: the first drawing exactly as it renders alone, and over it, in one 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. The color is the first of red, purple, blue, green and orange that the drawing itself does not come close to, and the answer says which (proposal_color); --proposal-color gives one instead, and when the drawing uses colors close to the one given, 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 pixels of the image in a PNG, and pixels on the screen in an SVG:

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 color of your own
uncad redline plan.dwg step2.json -o review.png --fit 1200 --stroke 3 --paper dark  # 3 px lines, black page
uncad redline plan.dwg step2.json -o review.svg --stroke 1 --paper dark  # 1 px on screen at any zoom

uncad mcp serves summarize, hit-test, relate, 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"] } } }

Scope

  1. DWG — read through LibreDWG (GPLv3+), bound directly via Rust FFI (bindgen). All versions.
  2. DXF — ASCII and binary, R2007 and later included, read by the pure-Rust undxf crate (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").
  3. Output — the parsed model as JSON (CadDatabase::to_json, from uncad-model). SVG and PNG come from the iron-render-cad crate (MIT), which uncad-cli uses. Writing DWG/DXF is not offered; the write API that existed in 0.1.0 was removed (see CHANGELOG.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 model
  • docs/CAVEATS.md — entity type coverage, known limitations and bugs, cross-platform notes
↑↓ navigate openesc close