# cadmpeg
`cadmpeg` inspects native CAD containers, dumps supported model data into
CADIR, checks and compares CADIR models, and exports CADIR, STEP Part 21
(AP203, AP214, or AP242), and bounded IGES 5.1, 5.2, or 5.3 Fixed ASCII. It also writes
supported `.FCStd`, `.f3d`, `.sldprt`, and `.3dm` models.
Native codecs transfer different subsets of geometry, topology, design intent,
presentation, and metadata. Check [format support][support] before relying on a
conversion path.
## Install
Install with Homebrew:
```sh
brew install cadmpeg/tap/cadmpeg
```
Or use the installer for macOS and Linux:
```sh
curl --proto '=https' --tlsv1.2 -LsSf \
Or install from crates.io:
```sh
cargo install cadmpeg
```
## Convert a model
`convert` decodes the input, checks the resulting CADIR, then exports it:
```sh
cadmpeg convert bracket.f3d -o bracket.step
cadmpeg convert bracket.f3d -o bracket.ap242.step --to step:ap242-e3
cadmpeg convert bracket.f3d -o bracket.step --reject-lossy=export
cadmpeg convert bracket.f3d -o bracket.igs --to 5.3
cadmpeg convert bracket.f3d -o bracket.3dm --to rhino:archive-80
```
### `--to FORMAT[:DIALECT]`
One flag names the output format and, when you want to say so, the dialect of
it. `--to` is a spelling of `-f`/`--format`; the three are the same flag.
- `--to rhino:archive-80` names both halves. The format half may use any
spelling `--to` accepts, so `--to 3dm:archive-80` is the same request.
- `--to step` names the format alone. It says which kind of file to write, not
which dialect of it, so a same-format conversion still preserves the input's
dialect.
- `--to 5.1` names a dialect of the format the output path implies. Each
format's own short vocabulary works here: `5.1` for IGES, `60` for Rhino,
`ap242e3` for STEP. A bare value is read as a format first, and no dialect
spelling collides with a format name.
An output extension that names a dialect selects it when `--to` names no
dialect: `-o assembly.usdz` writes the `usd:usdz` package, with or without
`--to usd`. An explicit dialect wins over the extension.
Otherwise, omitting `--to` is the identity default: converting a file to its own format
keeps the dialect it already is, and converting across formats writes the
target format's default. `cadmpeg dialects FORMAT` lists every id, and
`cadmpeg inspect FILE` prints the id of the file in front of you — the same
string you would pass to `--to`.
The CLI inspect path calls `cadmpeg_registry::resolve_and_inspect_with`.
`cadmpeg_registry::identify` is the candidate-reporting entry point: it retains
equal-confidence candidates instead of resolving them to one source.
A dialect the writer cannot produce is refused by name, with the whole catalog
of what it can produce, rather than silently rewritten. For IGES this covers
Compressed ASCII and Binary sources: converting one back to IGES replays the
original bytes, which the writer could not synthesize, and asking for a
different target is a synthesis request that says so.
The output extension selects `step`, `iges`, `fcstd`, `f3d`, `sldprt`, `rhino`, or
`cadir`. Pass `--to` when the filename does not identify the format, or when a
text format (`cadir`, `step`) goes to standard output:
```sh
cadmpeg convert bracket.f3d --to step > bracket.step
```
Binary formats (`fcstd`, `f3d`, `sldprt`, `rhino`) are refused on standard
output, because that spelling is nearly always `--input-format` (alias
`--from`) written as `--format`; pass `-o FILE` or, for a deliberate binary
pipe, `--binary-stdout`.
Conversion stops before export if the check finds errors in what the output
writes. An error in data the output format does not carry, for example
feature history in a STEP export, stays in the report and does not stop the
write. `--allow-errors` writes the current result anyway. It leaves model data
unchanged. Conversion writes the available output when decoding transfers no
geometry and reports the decode and export losses.
`convert` decodes a native source only as deeply as the output needs: STEP,
IGES, and Rhino output decodes geometry and skips design intent, so the decode
report lists no loss in sketches, features, or history. `--full-decode`, and
every `--reject-lossy` scope, decode the whole source whatever the output.
## Inspect, dump, and check
Inspect a native container without decoding its model:
```sh
cadmpeg inspect bracket.f3d
cadmpeg inspect bracket.f3d --json
```
Dump a native file to canonical CADIR JSON:
```sh
cadmpeg dump bracket.f3d -o bracket.cadir.json
```
Without `--output`, artifact-producing commands write only the artifact to
standard output. Diagnostics and loss summaries go to standard error, so
redirection remains safe.
Check either CADIR or a supported native file:
```sh
cadmpeg check bracket.cadir.json
cadmpeg check bracket.f3d --json
```
`check` also measures the model and reports four defect classes as
warnings: a face ring whose coedges are not listed in traversal order
(`ring_order`), a coedge whose pcurve uses are listed from its end to its
start (`pcurve_order`), a face whose sense disagrees with its loop winding,
and a closed body whose volume readings conflict (`orientation`). The pass has its
own allowance, much smaller than that of `measure`, and `--limits service`
makes it smaller still. When the allowance runs out, one `info` finding
(`measurement_incomplete`) states how many faces and bodies were not
checked. These findings do not change the exit status
([measurement contract][measurement]).
Stored geometric validation properties are compared with the measurements of
the part or bodies they describe. A difference beyond measurement numeric
error plus model uncertainty adds a `declared_measure` error and fails the
check. An unresolved target or unavailable quantity produces a warning with
its reason. STEP imports these declarations; CADIR preserves them.
`--container-only` stops native decoding before geometry and records container
depth. It is useful for container diagnostics. `convert` writes such a document
to CADIR only: every other output refuses it (`insufficient_depth`, exit 1).
## Read raw bytes
`cadmpeg inspect` also carries byte tools that work on any file, decoded or
not. Every offset and length argument accepts `0x` hexadecimal or decimal, and
`_` between digits.
```sh
cadmpeg inspect hex part.prt --offset 0x40 --len 0x80 # dump with an ASCII gutter
cadmpeg inspect read part.prt --type u32 --offset 0x40 # one scalar, decimal and hex
cadmpeg inspect read part.prt --type f64 --offset 0x100 --count 8 --stride 24
cadmpeg inspect find part.prt --encoding hex '4d5a??00' # `??` is a byte wildcard
cadmpeg inspect find part.prt --encoding utf16le Extrude
cadmpeg inspect strings part.prt --min 6 --encoding both
cadmpeg inspect struct part.prt --offset 0x100 --count 4 \
--layout 'u32le:id,pad4,f64le:x,f64le:y,f64le:z'
cadmpeg inspect container part.f3d # ZIP or CFB members
cadmpeg inspect extract part.f3d 'Design/Streams.dat' -o streams.dat
cadmpeg inspect cmp probe-a.prt probe-b.prt # positional byte compare
```
`--endian le` and `--endian be` select the byte order for `read`; little-endian is the
default. `read --count N` walks a record array, stepping `--stride` bytes and
defaulting to the scalar width.
Common alternative spellings are accepted: `--length` for `--len`, `--min-len`
and `--min-length` for `--min`, `--start` for `--offset`, `--step` for
`--stride`, `-n` for `--count`, and `--input FILE` for the positional file on
single-input tools. `cadmpeg inspect bytes <tool>` runs the same tool as
`cadmpeg inspect <tool>`. `find FILE NEEDLE --encoding hex|ascii|utf16le`
selects the encoding of the positional pattern. Text or hex values on
`read --type` produce an error that names the corresponding tool. `find` stops at `--max` hits and says so,
`--max 0` reports every hit, and `--context N` dumps `N` bytes around each
hit.
`--layout` is a comma-separated record spec with no implicit alignment:
| `u8`, `i8` | One byte. A byte-order suffix is rejected. |
| `u16le`, `i32be`, `f64le`, … | A wide scalar. The `le` or `be` suffix is required. |
| `bytesN` | `N` raw bytes, printed as hexadecimal. |
| `padN` | `N` bytes skipped and not printed. Takes no name. |
Every field except `padN` accepts an optional `:name`; unnamed fields are called
`f<index>`. `struct --count N` decodes `N` consecutive records and reports an
error rather than a partial record when the run passes end of file.
`inspect container` lists ZIP or CFB members. ZIP columns are
header/data/packed/unpacked/method/crc32/name, so a hex dump of an entry
follows directly. CFB columns are id/kind/size/alloc/path; size and alloc
are empty for storages. `--json` prints the same listing as versioned JSON
with raw names and `container_kind` `zip` or `cfb`. Names in the table are
single-quoted because Fusion `.f3d` entry names hold `[` and `]`, which a
shell reads as a glob. `inspect extract FILE MEMBER` writes one ZIP
entry's decompressed, CRC-checked bytes, or one CFB stream, to `-o FILE`
or standard output; the member name matches byte-exactly, brackets
included, where `unzip` reads it as a glob and fails. Other container
families are listed by `cadmpeg inspect FILE` through their codec.
`inspect cmp` compares byte `n` of one file with byte `n` of the other. It
reports the first differing offset, the differing byte count, and the differing
spans. `--gap` merges two spans separated by that many equal bytes or fewer.
It exits 1 if the files are not identical, including length-only (Unix cmp
contract). `cadmpeg diff` compares decoded models.
A file whose name is also a subcommand name, such as `hex`, is read as the
subcommand. Write `./hex` for such a file.
## Compare models
`diff` compares two decoded models structurally:
```sh
cadmpeg diff before.cadir.json after.cadir.json
cadmpeg diff before.f3d after.f3d --json
```
It reports unit, tolerance, arena membership, and modified entity fields.
Exit status `1` indicates a difference.
`diff --geometry` measures every body of both models in model space and
compares the bodies without entity IDs:
```sh
cadmpeg convert part.step -o part_rt.step --to step:ap242-e3
cadmpeg diff part.step part_rt.step --geometry
cadmpeg diff part.step part_rt.step --geometry --tsv
```
Area, volume, centroid, box, shell closure and orientation compare within a
tolerance made of both values' errors and a relative floor
(`--geometry-tolerance REL`, default `1e-9`). The verdict is `agree`,
`disagree` or `inconclusive`; exit status `1` means `disagree` or
`inconclusive`. `--tsv` prints one row per compared value; `--json` adds a
`geometry` member with both measurements and the comparison to the report.
Only body pairs that can match are compared, within a pair allowance that
`--limits` scales with the body count; a comparison that needs more is
refused. `docs/measurement.md` states the comparison rules.
## Measure models
`measure` reads a native CAD file or a CADIR document and measures it in
model space:
```sh
cadmpeg measure part.step
cadmpeg measure part.step --faces --kind cylinder --tsv
cadmpeg measure part.step --face face#46 --json
```
Without a face, edge or surface selector, it prints one row per body and a
document block. `--face ID` and `--edge ID` print rows for those entities;
`--faces` and `--edges` print a row for every face or edge in scope.
`--surface ID` prints the carrier row of a surface: its parameters and the
faces and bodies that use it, with no bounded measure. `--body ID` limits
the scope. `--occurrence ID` measures in the instance space of an assembly
occurrence: the scope is the bodies of its prototype and of the
prototypes of its subtree, each placed by its own transform and then by
the occurrence chain from the root, and every row states its `placement`
space. When the space cannot place its bodies, the report has no row and
the document totals are unavailable with the reason. `diff --geometry` stays in model space. `--kind KIND[,KIND...]` and `--radius RANGE` (`MIN..MAX`,
`MIN..`, `..MAX`, or `R`) filter face rows. An ID is an exact ID or a unique
ID suffix, as for `query item`.
A face row gives the carrier kind and parameters, the trimmed area, the
boundary and seam lengths, the box, the loop roles and the orientation
reading. A body row gives the area, the volume and centroid of a closed
body, its second moments of volume about the centroid (mm⁵) with their
principal moments and axes, and, with `--density VALUE` in kg/m³, its mass
(kg) and inertia tensor about the centroid (kg·mm²). Every value has a unit, a method, a numeric error and a model
uncertainty, or is unavailable with a pinned reason code and a category.
[`docs/measurement.md`][measurement] is the contract of the values, the
reason codes and the caveats.
`--tsv` prints one table with units in the column names: the body table,
the face table, the edge table, or the surface table. Each row states its
placement space in `space` (`model`, `occurrence` or `stored`) and
`occurrence`; `carrier_placement` in the surface table is the placement
class of the carrier. It refuses a selection
of more than one of faces, edges and surfaces. `--json` prints the versioned envelope; `--report FILE` (also `-o`
and `--output`) writes it to a file. `--limits service` selects the smaller
work allowance of a measurement pass. A CADIR document decoded at container
depth holds no geometry; `measure` refuses it with `insufficient_depth`.
## Inputs and outputs
The built-in codecs recognize `.f3d`, `.FCStd`, `.ipt`, `.iam`, `.sldprt`,
`.3dm`, `.CATPart`, IGES, STEP, bare ASM/ACIS `.sat`/`.smt`/`.smb`/`.sab`
streams, the NX and Creo `.prt` layouts, Creo View `.pvz` packages, and
OpenUSD `.usda` text layers by content. Commands that load models also accept CADIR JSON. Use
`--input-format` (alias `--from`) to bypass
detection for an ambiguous or extensionless input. Every command that takes
one input file also accepts `--input FILE` as a spelling of the positional;
the two-file commands (`diff`, `inspect cmp`, `inspect extract`) take their
inputs positionally only.
Output formats are:
- `cadir` for canonical CADIR JSON; `json` is an alias.
- `step` for ISO 10303-21 Part 21; `iges` for IGES fixed ASCII; `usd` for
OpenUSD USDA text layers.
- `fcstd`, `f3d`, `rhino`, and `sldprt` for the native writers' supported subsets.
`cadmpeg formats` lists what this build reads and writes. `cadmpeg dialects
[FORMAT]` lists the dialects of each format: the id, how well cadmpeg reads it,
whether anything writes it, and which of them are write targets of this build's
encoders.
Native writers use retained source data where the format requires it, and reject
unsupported edits. The [format support page][support] defines each reader and
writer's current semantic coverage.
File output is atomic. cadmpeg refuses to replace its input or an existing
output unless `--force` is present. An explicit `--to` format takes precedence
over a conflicting output extension and emits a warning.
## Losses and machine-readable reports
Native decoding prints whether geometry transferred and lists known losses.
STEP export reports omitted, reduced, or normalized content. To save a
versioned JSON record of a `dump` or `convert` operation, pass
`--report`:
```sh
cadmpeg convert bracket.f3d -o bracket.step \
--report bracket.conversion.json
```
The command report contains decode, check, and export sections when those
stages ran. `inspect --json`, `inspect container --json`, `check --json`,
`diff --json`, and `measure --json` write versioned JSON directly to standard
output.
`inspect`, `check`, `diff`, and `measure` produce no artifact beside the
report, so they
also accept `-o` and `--output` for the report path. Every command refuses to
replace an existing report or artifact unless `--force` is present. Check
findings live under `.check_report.findings`; `cadmpeg check` prints the
error count and exits 1 on the same `error` and `blocking` findings.
## Query reports
`query` projects one named view from a JSON artifact without `jq`: it reads a
command report, a decoded CADIR document, or a `<stem>.fidelity.json` decode
sidecar, detects which one it was given, and prints the view. Aggregate views
print tab-separated rows with a header; `item`, `graph`, and `join` print
pretty-printed JSON records.
```sh
cadmpeg check bracket.f3d -o report.json
cadmpeg query findings report.json # severity check entity message
cadmpeg query losses report.json # severity code message
cadmpeg query coverage report.json # decode coverage counts
cadmpeg query counts bracket.cadir.json # per-arena entity counts; alias: arenas
cadmpeg query item bracket.cadir.json model.faces FACE_ID # one record; alias: record
cadmpeg query schema types model.features # the arena's IR record type (no FILE)
cadmpeg query schema file part.cadir.json native.nx.class_definitions # inferred native fields
cadmpeg query graph bracket.cadir.json model.features ID --hops 1
cadmpeg query graph bracket.cadir.json model.features ID --follow native_ref --reverse
cadmpeg query join bracket.cadir.json model.features native.rhino.unknowns \
--left-key native_ref --right-key id
cadmpeg query summary report.json # artifact kind and section counts
cadmpeg query summary part.measure.json # measure rows per level, unavailable per category, undefined
cadmpeg query fidelity part.cadir.fidelity.json # retained source records
cadmpeg query fidelity part.cadir.fidelity.json --stream S -o s.bin # extract
```
`counts` on a CADIR document lists arena lengths for `model` and every
`native.<codec>` namespace; on a check report it lists `entity_counts`.
`item` uses the same dotted arena names as `query counts --json`
(`model.<arena>` or `native.<codec>.<arena>`; a bare name means
`model.<arena>`). It matches the JSON-string `id` field exactly or as a unique
suffix, accepts several IDs in one call, and with no ID prints the first
record (`--head N` for the first N). `--fields a,b.c` projects those paths as TSV
(projection only — no `--where`). `graph` starts from the same FILE ARENA
[ID...] selection and walks identity references: a string is an edge when it
equals another record's `id` in this document. `--hops N` (default 1) is the
maximum edge length; `0` emits only the starts. `--follow PATH,...` keeps
those field paths only (exact match). `--reverse` walks incoming references.
`--max-paths` (default 10000) truncates with a note on standard error and
still exits 0. `join` matches two arenas on required `--left-key` and
`--right-key` paths (`matched` inner join, `unmatched` left anti-join, `all`
grouped rights). `--right-file` joins two documents by key value only. `schema`
with no file prints the IR's compile-time record type
for a model arena — every field, whether it is required, and every variant of
a tagged union — or, bare, every model arena and its element type (`sidecar`
prints the decode-sidecar shape). `schema FILE ARENA` infers native (and
other) arena fields from the records: each dotted path's presence, JSON type,
an example, and a `relation` column (`id` for the record identity, `ref` /
`refs` for fields whose values resolve to a record id in this document or
match the identity grammar). Use `relation=ref|refs` paths as graph
`--follow` fields and as join keys. An unknown arena name lists every
addressable arena and its entry count. Which arenas a document actually has
also comes from `counts`.
`fidelity` lists a decode sidecar's retained source records (the extraction
address space) and, with `--stream NAME`, reassembles that stream's retained
bytes byte-exactly into `-o FILE` — refusing gapped extents and extent-only
retention loudly rather than splicing or writing empty output.
An empty or not-run section is not an error: the header prints, a
note goes to standard error, and the exit status stays `0`. A view the
artifact kind can never carry exits `2` and names the command that produces
the right artifact. `--json` wraps the projection in the versioned envelope,
and `-` reads standard input.
## Exit status
Verdict commands exit 1 on a negative verdict. `dump` and `measure` exit 1
on a semantic refusal of the input. Exit 2 is operational on every command.
- `0`: the requested operation completed; a verdict command had a positive
verdict when it ran.
- `1`: negative verdict. Verdict commands are `convert` (refused write),
`check` (failed check), `diff` (models differ), and `inspect cmp` (files
differ). `inspect cmp` exits 1 if the files are not identical, including
length-only (Unix cmp contract). `dump` and `measure` exit 1 on a semantic
refusal of the input: `strict_decode_rejected` under `--no-salvage`,
`unsupported_dialect`, or `insufficient_depth`.
- `2`: operational error, including invalid arguments, unrecognized input,
decode or encode failure, and file-system errors.
Non-verdict commands are `dump`, `query`, `measure`, and `inspect` (except
`inspect cmp`). For `measure`, an unavailable quantity is data, not a
failure.
Run `cadmpeg help <command>` for the complete options of a command.
## More documentation
- [Format support][support]
- [Architecture][architecture]
- [Contributing][contributing]
- [Clean-room and legal policy][legal]
- [Repository][repo]
Requires Rust 1.88 or later. Licensed under Apache-2.0.
[architecture]: https://github.com/cadmpeg/cadmpeg/blob/main/docs/architecture.md
[contributing]: https://github.com/cadmpeg/cadmpeg/blob/main/CONTRIBUTING.md
[legal]: https://github.com/cadmpeg/cadmpeg/blob/main/LEGAL.md
[measurement]: https://github.com/cadmpeg/cadmpeg/blob/main/docs/measurement.md
[repo]: https://github.com/cadmpeg/cadmpeg
[support]: https://github.com/cadmpeg/cadmpeg/blob/main/docs/format-support.md