libredwg-sys 0.15.0

Raw FFI bindings to the LibreDWG C sources (a vendored subset of upstream carrying four local patches, see NOTICE.md), built natively via cc + bindgen (no Emscripten/WASM).
Documentation
# uncad

[![CI](https://github.com/iyulab/uncad/actions/workflows/ci.yml/badge.svg)](https://github.com/iyulab/uncad/actions/workflows/ci.yml)
[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](LICENSE)

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

```bash
git submodule update --init   # test fixtures only -- not needed to build (see "Platform")
cargo build --workspace
cargo test --workspace
```

```rust
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.

```bash
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`](https://github.com/iyulab/iron-scout-cad) or
[`iron-diff-cad`](https://github.com/iyulab/iron-diff-cad), serialized as it is:

```bash
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:

```bash
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:

```bash
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
```

`balloon` draws numbered balloons on top of a drawing: the drawing exactly as it
renders alone, and over it, in one color, a circle holding each label you give,
joined by a leader to the entity or point it numbers. The labels are yours,
written as given; the verb decides only where each circle sits. It writes an SVG
file -- never over an existing one -- and answers with every balloon drawn (a
`crowded` one overlaps a text or another balloon, since no free spot was found)
and every one that could not be, and why. The balloons are JSON, as text or
`@file.json`: an array of `{ "label", "target": {"entity": <ID>} or {"point": [x, y]},
"state": "active" or "dropped" }`:

```bash
uncad balloon part.dwg --balloons '[{"label":"1","target":{"entity":42}}]' -o numbered.svg
uncad balloon part.dwg --balloons @balloons.json -o numbered.svg --color '#0057b8'  # a color of your own
uncad balloon part.dwg --balloons @balloons.json -o numbered.svg --size 12 --space paper  # 12 units across, in paper space
```

`uncad mcp` serves `summarize`, `hit-test`, `relate`, `diff`, `set`, `redline` and `balloon` as [Model Context
Protocol](https://modelcontextprotocol.io) 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.

```json
{ "mcpServers": { "uncad": { "command": "uncad", "args": ["mcp"] } } }
```

## Scope

1. **DWG** — read through [LibreDWG]https://www.gnu.org/software/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`]./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`]https://github.com/iyulab/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`]./CHANGELOG.md).

The model itself -- `CadDatabase`, `Entity`, `Tables` and their JSON form -- is
the [`uncad-model`](https://github.com/iyulab/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`](./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`]./docs/ARCHITECTURE.md — crate layout, build system,
  test layout, the FFI/bindgen boundary, thread safety, the entity model
- [`docs/CAVEATS.md`]./docs/CAVEATS.md — entity type coverage, known
  limitations and bugs, cross-platform notes