Introduction
cadvm (CAD Version Manager) is a local-first version control system for CAD files — STEP/STP (B-Rep) and STL/OBJ (triangle mesh). It brings Git-like workflows — snapshots, branches, diff, checkout, revert — to CAD data, and goes further with a geometric diff and a 3D viewer that show what actually changed in the geometry, not just in the bytes.
cadvm init
cadvm snapshot -m "Cube with a Ø5 hole"
# …edit the part…
cadvm snapshot -m "Ø5 → Ø8"
cadvm geom-diff HEAD~1 HEAD # volumes added / removed / common
cadvm view HEAD~1 HEAD # interactive 3D diff in your browser
cadvm ui # full-screen terminal dashboard
Why cadvm?
CAD files are awful to version with Git: a tiny geometric edit re-exports the whole STEP file with renumbered entities and a new timestamp, so a textual diff is pure noise. cadvm instead:
- stores versions deduplicated (content-addressed, gzip-compressed chunks);
- summarizes each file with light metadata (STEP schema/entities, or mesh triangle/vertex counts);
- computes a real geometric diff (added / removed / common) — via Open CASCADE for STEP/STP, and a pure-Rust triangle diff for STL/OBJ;
- renders that diff in a self-contained 3D HTML viewer (no server, no cloud).
🧊 See it live: the Live demo embeds a real diff you can rotate and zoom right in your browser.
Architecture
cadvm is built in three layers:
| Layer | What | Tech |
|---|---|---|
| VCS core | commits, manifests, refs, branches, status, diff, checkout, storage; STL/OBJ mesh diff | 100% Rust |
| Geometry helper | STEP/STP added/removed/common volumes + face diff, tessellation | C++ / Open CASCADE (subprocess) |
| Viewer | interactive 3D diff | self-contained WebGL HTML |
The Rust core never links Open CASCADE directly — it calls the cadvm-geom
helper as a subprocess, keeping the heavy CAD dependency isolated. Open
CASCADE is needed only for the STEP/STP geometric diff; the VCS, the TUI and
the STL/OBJ mesh diff all work without it.
Crates
cadvm-store— content-addressed storage (BLAKE3, blobs, chunks).cadvm-core— repository logic.cadvm-cli— thecadvmbinary (CLI + TUI).cpp/cadvm-geom— the C++/OCCT geometry helper.
The API reference (rustdoc) documents these crates.
Continue with Installation.
cadvm for AI agents
If your stack generates or edits CAD with AI (text-to-CAD, parametric copilots, design agents), cadvm is the version / diff / verification layer underneath it. The AI produces geometry; cadvm pins every iteration, tells your agent what actually changed, and lets it accept or roll back — automatically.
A human reviews a diff by eye. An agent needs structured data — that is what
cadvm geom-diff --json provides.
▶ See it run: the Example: AI agent loop page replays this whole loop (accept a good edit, catch & revert a regression) with its real output — no LLM, no Open CASCADE.
The agent loop
cadvm init # once, in the working directory
# …the AI writes/edits a CAD file (part.step, part.stl, …)…
cadvm snapshot -m "iteration 7" # pin this AI iteration
# Machine-readable geometric diff vs the previous iteration:
cadvm geom-diff HEAD~1 HEAD --json
{
"rev_a": "9c1f…", "rev_b": "a3b2…",
"files": [{
"path": "part.step",
"kind": "brep",
"diff": {
"status": "ok",
"added": { "volume": 173.79, "faces": 252 },
"removed": { "volume": 109.91, "faces": 179 },
"common": { "volume": 6266.98, "faces": 157 },
"faces_topo": { "common": 6, "added": 3, "removed": 1 }
}
}]
}
Your agent parses that and decides — or it lets cadvm judge directly with
verify, which asserts expectations and returns pass/fail (exit code 0/1):
# "the edit should add material and remove almost none"
cadvm verify HEAD~1 HEAD --expect 'added_volume>50' --expect 'removed_volume<1' --json
# → {"report":{"pass":true,"checks":[...]}} exit 0 = pass, 1 = fail
So the agent can:
- verify / gate — accept an iteration only if
cadvm verifypasses; - revert —
cadvm revert HEADto undo a bad generation, then retry.
Available metrics: added_volume, removed_volume, common_volume,
volume_delta, faces_added/removed/common (STEP); added_tris,
removed_tris, unchanged_tris, bbox_dx/dy/dz (STL/OBJ).
Mesh files (STL/OBJ) emit the same shape with unchanged / added / removed
triangle layers — and need no Open CASCADE (pure-Rust diff).
Evals & CI gates (no repository)
For evals and CI you usually have two files — the model’s output and a
reference — and just want a geometric pass/fail. --files compares them
directly, no repo, no snapshots:
# Did the candidate match the reference closely enough?
cadvm verify --files candidate.stl reference.stl \
--expect 'added_tris<5' --expect 'removed_tris<5'
echo $? # 0 = pass (gate the model output), 1 = fail
cadvm geom-diff --files candidate.step reference.step --json # raw signal
cadvm view --files candidate.stl reference.stl # 3D diff for a human
This makes cadvm verify a drop-in geometric assertion for any eval harness,
RL reward, or CI job — exit code in, JSON out.
As a GitHub Action
A ready-made action gates generated CAD in a pull request — it downloads cadvm
and runs verify --files, failing the job if the geometry drifts:
- uses: AdeMBCH/cadvm/.github/actions/cadvm-verify@main
with:
file-a: reference.stl
file-b: candidate.stl
expect: |
added_tris<10
removed_tris<10
Full sample workflow: examples/ci-gate.yml.
(STL/OBJ work out of the box; STEP/STP need Open CASCADE on the runner.)
Why it fits AI workflows
- Structured feedback —
--jsonis a reward/verification signal for agents, eval harnesses and RL loops, not just a human-readable report. - Local-first & offline — no cloud, no account; runs in CI or inside a sandbox next to the model.
- Deterministic & cheap — content-addressed storage dedupes the many near-identical iterations an agent produces.
- Agent-friendly surface — a plain CLI with JSON output, easy to wrap as a tool (e.g. an MCP server) the model calls.
- Visual check for humans —
cadvm viewrenders the same diff in 3D when a person needs to look.
Also useful for
- Evals / benchmarks for CAD-generating models — score “did the model produce the intended geometric change?”.
- Regression gates in CI for generated or parametric CAD.
Use it via MCP (no glue code)
cadvm ships an MCP server so an agent calls it as native tools — no subprocess wiring or output parsing. Register it with your MCP client:
{
"mcpServers": {
"cadvm": { "command": "cadvm", "args": ["mcp"] }
}
}
(With Claude Code: claude mcp add cadvm -- cadvm mcp.) It speaks JSON-RPC 2.0
over stdio — local, offline, no server to host.
The model then sees these tools:
| Tool | Does |
|---|---|
cadvm_status | new / modified / deleted vs HEAD |
cadvm_snapshot | pin an iteration (commit) |
cadvm_log | history |
cadvm_diff | metadata diff |
cadvm_geom_diff | geometric diff (added/removed/common) |
cadvm_verify | assert expectations → pass/fail |
cadvm_revert | undo the last iteration |
cadvm_compare_files | geometric diff of two files (no repo — for evals) |
cadvm_verify_files | assert expectations on two files (no repo) → pass/fail |
Each tool takes an optional repo argument (the working directory); otherwise it
uses the server’s current directory. So the loop above becomes a sequence of
tool calls the model makes on its own.
Example: AI agent loop
A runnable, self-contained demo of cadvm as the version / diff / verify layer under an AI-CAD agent — accept a good edit, catch and revert a regression. No LLM and no Open CASCADE: the part is an STL, so the geometric diff is pure Rust.
Script: examples/agent-loop.sh.
examples/agent-loop.sh # after building or installing cadvm
What it runs
cadvm init
cadvm snapshot -m "baseline bracket"
# Agent adds a mounting boss → GATE: it must add material
cp block_v2.stl bracket.stl
cadvm snapshot -m "agent: add mounting boss"
cadvm verify HEAD~1 HEAD --expect 'added_tris>0' # exit 0 = accept
# Agent regresses and drops the boss → GATE: nothing significant removed
cp block_v1.stl bracket.stl
cadvm snapshot -m "agent: regenerate (drops the boss)"
cadvm verify HEAD~1 HEAD --expect 'removed_tris<20' \
|| cadvm revert HEAD # exit 1 = revert
The accept/revert decision is driven by cadvm verify’s exit code — exactly
the hook an agent or a CI gate uses.
What it prints
● baseline bracket committed
▶ Agent iteration: add a mounting boss
verifying added_tris > 0 …
✓ added_tris > 0 (actual 158)
PASS (1 checks)
✓ accepted
▶ Agent iteration: (buggy) regenerates and loses the boss
verifying removed_tris < 20 …
✗ removed_tris < 20 (actual 158)
FAIL (1/1 checks failed)
✗ regression caught — reverting to the good version
▶ The JSON an agent parses (verify --json):
{
"file": "bracket.stl",
"report": {
"pass": true,
"metrics": { "added_tris": 158, "removed_tris": 64, "unchanged_tris": 114,
"bbox_dx": 40, "bbox_dy": 30, "bbox_dz": 28 },
"checks": [ { "metric": "added_tris", "op": "Gt", "expected": 0,
"actual": 158, "pass": true } ]
}
}
▶ History (the bad iteration was reverted, the boss survives):
commit … Revert "agent: regenerate (drops the boss)"
commit … agent: regenerate (drops the boss)
commit … agent: add mounting boss
commit … baseline bracket
In an agent (via MCP)
The same loop runs as native tool calls — cadvm_snapshot, cadvm_verify,
cadvm_revert — when cadvm is wired in as an MCP server.
Live demo
Below is a real cadvm view output. Between the two versions of this block a
hole was moved from the left to the right, and a boss was added on top.
It is rendered live in your browser; nothing is installed or sent anywhere, the
whole 3D scene is embedded in the page.
The unchanged body is drawn translucent grey so you can see the changes — including the ones inside the part:
- green — added: the new hole (visible through the body) and the boss on top;
- red — removed: the old hole on the left;
- grey — unchanged faces (the block body keeps the same surfaces).
Drag to rotate, scroll to zoom, and toggle any layer in the panel.
This page is exactly what cadvm view HEAD~1 HEAD produces — a single,
self-contained HTML file. See 3D viewer for how to generate one for
your own parts, and Geometric diff for the numbers behind it.
Installation
cadvm installs in two layers. The first is all most users need.
- the
cadvmbinary (version control + TUI) — pure Rust, works everywhere; - the
cadvm-geomhelper (geometric diff + viewer) — requires Open CASCADE, a prerequisite you install yourself (cadvm does not bundle it).
You only need part 2 for cadvm geom-diff and cadvm view; everything else
works without Open CASCADE.
1. The cadvm binary
One-line install (Linux / macOS)
curl -fsSL https://raw.githubusercontent.com/AdeMBCH/cadvm/main/scripts/install-release.sh | sh
This detects your OS/arch, downloads the matching binary from the
latest release, and installs
it into ~/.local/bin (override the location with CADVM_INSTALL_DIR).
Prebuilt binary (manual download)
Each release ships binaries for Linux, macOS (Apple Silicon) and Windows.
Download the one for your platform and put it on your PATH:
# Linux
curl -L -o cadvm https://github.com/AdeMBCH/cadvm/releases/latest/download/cadvm-x86_64-unknown-linux-gnu
chmod +x cadvm && sudo mv cadvm /usr/local/bin/
(macOS: cadvm-aarch64-apple-darwin; Windows: cadvm-x86_64-pc-windows-msvc.exe.)
From source
Requires a recent stable Rust toolchain (tested on 1.96).
# from a clone of the repository
cargo install --path crates/cadvm-cli
This puts cadvm in ~/.cargo/bin. Make sure that directory is on your PATH:
export PATH="$HOME/.cargo/bin:$PATH"
cadvm --help
To update later, re-run the same command with --force.
2. Geometry features (Open CASCADE prerequisite)
Install Open CASCADE
Ubuntu / Debian:
sudo apt-get install -y \
libocct-foundation-dev libocct-modeling-data-dev \
libocct-modeling-algorithms-dev libocct-data-exchange-dev \
cmake g++
macOS (Homebrew):
brew install opencascade cmake
Windows: install OCCT (e.g. via vcpkg opencascade) and CMake, then build
from a Developer prompt.
Build the helper
cpp/build.sh # produces cpp/cadvm-geom/build/cadvm-geom
Point cadvm at it
export CADVM_GEOM_BIN="$PWD/cpp/cadvm-geom/build/cadvm-geom"
Add that line to your ~/.bashrc (or shell profile) to make it permanent. If
cadvm-geom is on your PATH, the env var is optional.
One-command install / uninstall
From a clone, on Linux/macOS:
./scripts/install.sh # builds & installs cadvm; also builds the geometry
# helper when Open CASCADE is detected
./scripts/uninstall.sh # removes the binary and the geometry build
uninstall.sh removes the cadvm binary (via cargo uninstall cadvm-cli) and
the locally built helper. Your repositories’ .cadvm/ data is left untouched;
if you exported CADVM_GEOM_BIN in your shell profile, remove that line.
Shell completions
cadvm can generate completion scripts for bash, zsh, fish, elvish and
PowerShell:
# bash
mkdir -p ~/.local/share/bash-completion/completions
cadvm completions bash > ~/.local/share/bash-completion/completions/cadvm
# zsh (ensure the dir is in your $fpath, then recompinit)
cadvm completions zsh > ~/.zfunc/_cadvm
# fish
cadvm completions fish > ~/.config/fish/completions/cadvm.fish
Reopen your shell, then cadvm <Tab> completes commands and options.
Verifying
cadvm --help # lists all commands, including ui
cadvm completions bash | head
cadvm-geom diff a.step b.step # if you built the helper
Next: Getting started.
Getting started
This walks through a full session on a real part.
Create a repository
mkdir my-part && cd my-part
cadvm init
This creates a .cadvm/ directory. cadvm tracks .step, .stp, .stl and
.obj files
recursively from here (other files are ignored — see
Storage model).
Set your identity (optional but recommended)
cadvm config user.name "Your Name"
cadvm config user.email "you@example.com"
Commits made afterwards record this author. You can also override it per-command
with the CADVM_AUTHOR_NAME / CADVM_AUTHOR_EMAIL environment variables.
First snapshot
Drop a STEP file in the directory, then:
cadvm status # piece.step shows under "New"
cadvm snapshot -m "Initial version"
A snapshot captures the whole working tree (there is no staging step).
Make a change and snapshot again
Re-export the part from your CAD tool over the same file, then:
cadvm status # piece.step shows under "Modified"
cadvm snapshot -m "Enlarged the main bore"
Inspect history
cadvm log # commits, newest first, with author + date
cadvm show HEAD # one commit in detail + per-file STEP metadata
cadvm diff HEAD~1 HEAD # metadata diff (size, lines, entities, schema)
See the geometry change
With the geometry helper built:
cadvm geom-diff HEAD~1 HEAD # added / removed / common volumes + face counts
cadvm view HEAD~1 HEAD --open # 3D diff in your browser
Or do it all interactively
cadvm ui
A full-screen dashboard to browse commits and launch diffs/viewer — see Interactive dashboard.
Next: Typical workflow.
Typical workflow
Branching to explore a variant
cadvm branch second-hole # create a branch at HEAD
cadvm switch second-hole # move onto it (refuses a dirty tree without --force)
# …edit the part, then…
cadvm snapshot -m "Added a second hole"
cadvm switch main # back to the trunk
cadvm branch # list branches; * marks the current one
cadvm branch -d second-hole # delete a branch (not the current one)
switch restores the files of the target branch and never silently discards
local changes: it refuses when the working tree is dirty unless you pass
--force.
Undoing the last commit
cadvm revert HEAD
This creates a new commit that restores the state of HEAD’s parent (it does not rewrite history). Reverting HEAD is supported.
Restoring files without moving the branch
cadvm checkout HEAD~2 # restore the whole tree to that revision
cadvm checkout HEAD~2 -- piece.step # restore a single file
cadvm checkout HEAD~2 --force # discard local modifications
checkout is restore-like: it changes files on disk but does not move
HEAD or the current branch. It refuses to overwrite locally modified files
without --force, and never deletes untracked files.
Revisions you can name
Anywhere a revision is expected:
HEAD,HEAD~1,HEAD~2, … (andHEAD^)- a branch name
- a full 64-char hash (with or without the
blake3:prefix) - an unambiguous short-hash prefix
Reclaiming space
cadvm gc # report unreferenced objects (safe, no deletion)
cadvm gc --prune # actually delete them
gc --prune removes any objects not reachable from a branch (see
Storage model).
Next: Interactive dashboard.
Interactive dashboard (TUI)
cadvm ui
cadvm ui opens a full-screen terminal dashboard (built with
ratatui) — a source-control panel for your CAD history.
Layout
◆ cadvm /path/to/repo ⎇ main ● clean
╭ commits ───────────────────────────╮╭ details ──────────────────────╮
│▌● a81e2421 HEAD Bloc v2 · Mat … ││ commit a81e2421… │
│ ● c9cb67eb ⎇ main Bloc v1 · Mat … ││ author: Mat <…> │
│ ││ date: 2026-06-12 … │
│ ││ Files (1) │
│ ││ ▪ piece.step │
│ ││ 70134 B · 1794 lines … │
╰─────────────────────────────────────╯╰───────────────────────────────╯
↑↓ move m anchor d diff g geom v view b branch s status ? help q quit
- Left — the commit list: graph marker (
●, green on HEAD), short hash,HEADbadge, branch chips (⎇ name), message, author and relative time. - Right — details of the selected commit: hash, author, date, parents, message, and each file with its STEP metadata.
Keys
| Key | Action |
|---|---|
↑/k, ↓/j | move the selection |
m | set/clear the anchor (the diff base) |
d | metadata diff |
g | geometric diff (volumes + faces) |
v | build & open the 3D viewer |
b | switch branch |
s | working-tree status |
r | reload |
? | help · q/Esc quit or close a modal |
How diffs choose their two sides
d, g and v compare anchor → selected. If no anchor is set, they compare
the selected commit’s parent → selected (i.e. “what this commit changed”).
Set an anchor with m on one commit, move to another, then press d/g/v to
compare across an arbitrary range.
Notes
gandvneed thecadvm-geomhelper (CADVM_GEOM_BIN); if it is missing you get a red toast, not a crash.- The TUI calls the engine directly — it is the same binary as the CLI.
Geometric diff
The metadata diff (cadvm diff) never inspects geometry. cadvm diffs geometry
two ways, depending on the format:
- STEP/STP (B-Rep) — via
cpp/cadvm-geom, a standalone C++/Open CASCADE executable run as a subprocess (no FFI). Needs Open CASCADE; see Installation. - STL/OBJ (mesh) — a pure-Rust triangle diff built into
cadvm; no Open CASCADE required (see Mesh diff below).
B-Rep diff (STEP/STP)
What it computes
Given two STEP files A and B, it computes the boolean decomposition of their solids:
- common — material in both (A ∩ B);
- added — material in B not in A (B − A);
- removed — material in A not in B (A − B).
For each it reports a volume and face count, plus per-input metrics (volume, surface area, solid/shell/face counts, bounding box).
It also reports a topological face-to-face diff: faces of A and B are matched by their underlying surface — the plane equation, the cylinder’s axis and radius, the cone/sphere/torus parameters — which is invariant to how the face is trimmed. A wall that merely gains a hole keeps the same plane and counts as unchanged; only genuinely new or removed surfaces (e.g. the hole’s cylinder) are reported as added / removed. (Freeform B-spline faces fall back to an area + centroid signature.)
Using it
cadvm geom-diff HEAD~1 HEAD
Geometric diff 50d54d61..62cda376
piece.step
volume: 6498.700 -> 6584.340
area: 2709.870 -> 2902.720
bodies: 76 shells -> 190 shells
faces: 76 -> 190
bbox: 20.00×20.00×20.00 -> 20.00×20.00×20.00
common: vol 6266.980 (157 faces)
added: vol 173.788 (252 faces)
removed: vol 109.907 (179 faces)
faces (topo): 2 common, 188 added, 74 removed
By default it diffs every modified STEP file; restrict it with -- <file>.
Solids vs shells. Many STEP exports are sewn shells rather than OCCT
solids, sobodiesmay report shells. Volumes are still integrated correctly over the faces.
The JSON contract
cadvm-geom diff a.step b.step prints a JSON object to stdout (status,
file_a/b, a/b metrics, common/added/removed pieces, faces_topo). A
handled geometry failure prints {"status":"error", ...} and still exits 0, so
the caller always receives structured output.
Mesh diff (STL/OBJ)
Triangle meshes have no B-Rep faces or solids, so the boolean pipeline does not apply. Instead cadvm classifies each triangle by its distance to the other mesh — entirely in Rust, no Open CASCADE:
- a triangle of the new version whose centroid lies on the old surface (point-to-triangle distance below a tolerance) is unchanged;
- one with nothing nearby is added;
- an old-version triangle with nothing nearby in the new mesh is removed.
Using point-to-triangle distance (not vertex-to-vertex) means a shared face matches even when the two meshes triangulate it differently.
cadvm geom-diff HEAD~1 HEAD # works on .stl/.obj with no helper
part.stl
unchanged: 114 triangles
added: 158 triangles
removed: 64 triangles
bbox: 40.00×30.00×28.00
(distance-based mesh diff)
Mesh diffing is inherently fuzzier than the B-Rep diff (it depends on tessellation and a distance tolerance), but it needs no Open CASCADE and feeds the same 3D viewer.
To visualize either diff in 3D, see 3D viewer.
3D viewer
cadvm view HEAD~1 HEAD --open
cadvm view turns a geometric diff into a single self-contained HTML file
with a hand-written WebGL renderer — no CDN, no server, fully offline. Open
it in any modern browser.
What you see
Each face of the part is colored by how it changed:
- grey — unchanged (the face exists in both versions);
- green — added (a face of the new version with no match in the old one);
- red — removed (a face of the old version, gone in the new one).
So you see exactly which faces changed on the real part. Each layer can be toggled in the side panel.
Controls: drag to rotate · scroll to zoom · toggle layers with the checkboxes.
Options
cadvm view HEAD~1 HEAD # if exactly one file changed
cadvm view HEAD~1 HEAD -- piece.step # pick the file when several changed
cadvm view HEAD~1 HEAD -o diff.html # choose the output path
cadvm view HEAD~1 HEAD --open # also open in the default browser
You can also launch it straight from the TUI with the v key.
STEP/STP vs STL/OBJ
- STEP/STP (B-Rep): the
cadvm-geomhelper classifies each face by its underlying surface and tessellates them — needs Open CASCADE. - STL/OBJ (mesh): cadvm diffs the triangles directly in pure Rust (no
Open CASCADE). Each triangle of the new mesh is unchanged if it lies on the
old surface (point-to-triangle distance) and added otherwise; old triangles
with nothing nearby in the new mesh are removed. So
viewworks on meshes even without the helper installed.
Both paths emit the same unchanged/added/removed layers, so the viewer is identical.
Under the hood
The classifier emits flat-shaded, per-color triangle layers as JSON; cadvm embeds
that into the HTML template (cadvm-cli/src/viewer.rs) and the WebGL code renders
it with per-layer colors and transparency.
Command reference
Run cadvm <command> --help for full flags. All commands operate on the
repository discovered by walking up from the current directory.
| Command | Description |
|---|---|
cadvm init | Create a .cadvm/ repository in the current directory. |
cadvm snapshot -m "msg" | Record a snapshot (commit) of all tracked CAD files (STEP/STP/STL/OBJ). |
cadvm status | Show new / modified / deleted files vs. HEAD. |
cadvm log | Show the commit history of HEAD. |
cadvm show [<rev>] | Show one commit’s details and per-file metadata. |
cadvm diff [<a> <b>] [--json] | Metadata diff (default HEAD~1..HEAD); --json for scripts/agents. |
cadvm checkout <rev> [-- <file>…] | Restore the working tree (or named files) to a revision. |
cadvm branch | List branches. |
cadvm branch <name> | Create a branch at HEAD. |
cadvm branch -d <name> | Delete a branch. |
cadvm switch <name> | Switch branches, restoring their files. |
cadvm revert <rev> | Create a commit that restores HEAD’s parent state. |
cadvm gc [--dry-run | --prune] | Report / delete unreferenced objects. |
cadvm geom-diff <a> <b> [--json] [--files] | Geometric diff of modified files (STEP via OCCT; STL/OBJ pure Rust). --files compares two files on disk, no repo. |
cadvm verify <a> <b> --expect '<m><op><v>'… [--files] | Assert geometric expectations; exit 0/1 (AI gating & CI). --files for two files on disk. |
cadvm view <a> <b> | Generate a standalone 3D HTML viewer of the diff. |
cadvm ui | Interactive full-screen terminal dashboard. |
cadvm mcp | Run an MCP server over stdio — exposes cadvm as tools for AI agents. |
cadvm config [<key>] [<value>] | Get / set / list config (e.g. user.name). |
cadvm completions <shell> | Print a shell completion script. |
Common flags
--force— oncheckout/switch/revert: proceed even when it would overwrite locally modified files or a dirty tree. cadvm never overwrites your work without it.-- <files>— oncheckout/geom-diff/view: restrict to specific files.
Safety rules
switchandrevertrefuse a dirty working tree without--force.checkoutrefuses to overwrite a locally modified file without--force.- Untracked files are never deleted.
gconly deletes with--prune;gcalone is a dry run.
See Typical workflow for examples and the revision syntax.
Storage model
A repository lives entirely in .cadvm/:
.cadvm/
├── objects/
│ ├── chunks/ # fixed 256 KiB content chunks (file storage)
│ ├── blobs/ # whole-file blobs (optional; cleaned by gc)
│ ├── manifests/ # serialized snapshots
│ └── commits/ # serialized commits
├── refs/heads/<branch> # each file holds the branch's tip commit id
├── HEAD # "ref: refs/heads/main" or a detached commit id
├── config.json # user.name / user.email and other settings
├── index.json # size+mtime hash cache (speeds up status)
└── tmp/ # scratch space for atomic writes
Content addressing
Every object is identified by the BLAKE3 hash of its content
(blake3:<hex>), sharded by the first two hex byte-pairs:
.cadvm/objects/chunks/ab/cd/<full-hex>
Writes are atomic (temp file + rename), so a crash can never leave a corrupt object. Writing identical content twice is automatically deduplicated.
Deduplication
- Content identity —
raw_hashis the BLAKE3 hash of the whole file. Identical files share an identity, so status/diff comparisons are exact. - Fixed-size chunking — files are split into 256 KiB chunks, each stored content-addressed, so identical chunks are shared across files and versions.
Chunk-only storage
File content is stored as chunks; the whole file is not duplicated as a
standalone blob, so there is no on-disk redundancy. checkout reconstructs each
file by concatenating its chunks, and cadvm gc --prune removes any
unreferenced objects.
What is tracked
cadvm tracks .step, .stp, .stl and .obj files, recursively from the
repository root. Skipped: the .cadvm/ directory, hidden directories, and
anything matching .cadvmignore.
A .cadvmignore at the root uses a small pattern syntax:
# comments and blank lines are ignored
*.bak # glob on the file name (* and ? supported)
build/ # a directory and everything beneath it
/secret/old.step # leading "/" anchors to the repo root
Performance
Working files are hashed in a streaming fashion (fixed-size blocks), so even
very large STEP files are processed with constant memory. A hash cache in
index.json records each file’s size, modification time and content hash; on the
next status an unchanged file (same size + mtime) reuses its cached hash
instead of being re-read and re-hashed. The cache is a transparent optimization —
a miss simply recomputes — and a snapshot warms it so the following status is a
pure cache hit.
File metadata
cadvm does not parse geometry for the VCS — it surfaces cheap, useful
figures that feed show, log and the metadata diff:
- STEP/STP: line count,
HEADER;/DATA;sections,FILE_SCHEMA, the entity count, and the top 20 entity types. - STL/OBJ: triangle and vertex counts, and an axis-aligned bounding box.
Platform support
| Component | Linux | macOS | Windows |
|---|---|---|---|
VCS + TUI (init, snapshot, ui, …) | ✅ | ✅ | ✅ |
Geometry (geom-diff, view) | ✅ | ✅ | ✅ |
VCS core & TUI
Pure Rust and cross-platform by design. Platform-specific bits (the SIGPIPE
reset, opening the browser) are gated per-OS, so the binary builds and runs on
Linux, macOS and Windows. The TUI works in any modern terminal (Windows
Terminal, Terminal.app, common Linux terminals).
Geometry helper
cadvm-geom works wherever Open CASCADE and a C++17 toolchain are available
— all three OSes qualify — but the installation of OCCT differs per platform
(see Installation). It is only needed for the STEP/STP
geometric diff; the VCS, TUI, and the STL/OBJ mesh diff all work without it.
Tested status
The Rust binary (version control + TUI) is built and tested on Linux, macOS and Windows in CI on every push, and prebuilt binaries for all three are attached to each release.
The geometry helper has been built and exercised on Ubuntu 24.04 (OCCT 7.6).
On macOS/Windows you may need to adjust the OCCT toolkit names or CMake discovery
in cpp/cadvm-geom/CMakeLists.txt.
Limits & roadmap
Current limits
- The geometric diff matches faces by their underlying analytic surface (plane, cylinder, cone, sphere, torus); freeform B-spline faces use a coarser area + centroid fallback.
- cadvm cannot merge two concurrent edits of the same file.
- The STL/OBJ mesh diff depends on tessellation and a distance tolerance, so it is fuzzier than the B-Rep diff.
geom-diff/viewneed thecadvm-geomhelper (OCCT) only for STEP/STP; STL/OBJ diff in pure Rust. The rest of cadvm works without OCCT.
Done
- VCS core — snapshots, log, status, diff, branches, switch, revert, checkout, gc, config/author, deduplicated, gzip-compressed chunk storage.
- Geometric diff —
cadvm-geom(C++/OCCT): boolean volumes + metrics + surface-based face-to-face diff. - 3D viewer —
cadvm view: self-contained WebGL HTML, per-face green/red/grey changes. - STL/OBJ support — versioning + mesh metadata, and a pure-Rust distance-based mesh diff feeding the same 3D viewer (no Open CASCADE).
- AI/agent surface —
--jsondiffs, averifycommand (assert expected geometric deltas, exit 0/1), repo-less--filescomparison for evals/CI, a GitHub Action (cadvm-verify) to gate generated CAD, and an MCP server (cadvm mcp) exposing cadvm as native tools for agents. - Interactive TUI —
cadvm ui, and shell completions. - Cross-platform — CI on Linux/macOS/Windows and prebuilt release binaries.
- Docs — user guide and API reference published online.
Next
- Sharper mesh diff (point sampling beyond centroids, configurable tolerance).
- glTF/PLY mesh formats.
- A staging index and richer merge tooling.
Developing
Repository layout
cadvm/
├── crates/
│ ├── cadvm-cli/ # the `cadvm` binary (CLI + TUI + viewer template)
│ ├── cadvm-core/ # repository logic, geom bridge
│ └── cadvm-store/ # content-addressed storage
├── cpp/cadvm-geom/ # C++/OCCT geometry helper
├── docs/ # this mdBook (user guide)
└── tests/fixtures/ # sample STEP files
Everyday commands
cargo fmt
cargo test # geometry tests skip if CADVM_GEOM_BIN unset
cargo clippy --all-targets --all-features
To run the geometry-backed tests, point at a built helper:
export CADVM_GEOM_BIN="$PWD/cpp/cadvm-geom/build/cadvm-geom"
cargo test
API documentation (cargo doc)
The crates are documented with /// / //! doc comments. Generate the API
reference (for contributors) with:
cargo doc --no-deps --workspace --open
Add --document-private-items to include internal items. The build is warning
-clean under RUSTDOCFLAGS="-D warnings".
This is the developer/API doc. The user guide is this mdBook (below) — keep the two distinct: rustdoc explains the code, the book explains the tool.
📚 The API reference is published online alongside this guide: https://adembch.github.io/cadvm/api/cadvm_core/index.html (rebuilt on every push by the Docs workflow).
User guide (this book)
cargo install mdbook # once
mdbook serve docs --open # live-reloading preview
mdbook build docs # static site in docs/book/
The geometry helper
cpp/build.sh # needs Open CASCADE + cmake + a C++17 compiler
The Rust core talks to it only as a subprocess (see cadvm-core::geom); there is
no FFI. Keep OCCT confined to cpp/cadvm-geom.
Publishing to the MCP Registry
cadvm ships a server.json
describing it for the official MCP Registry
— the catalog MCP clients use to discover and install servers. This page is the
maintainer checklist to (re)publish it.
The registry hosts metadata only; the runnable artifact must live in a public package registry. cadvm uses the cargo path (crates.io).
Prerequisites
- A crates.io account + API token (
cargo login). - A GitHub account (namespace
io.github.AdeMBCH/*). - The publisher CLI:
brew install mcp-publisher.
1. Publish the crates to crates.io
cadvm is a workspace; publish bottom-up so dependencies resolve:
cargo publish -p cadvm-store
cargo publish -p cadvm-core # after cadvm-store is live
cargo publish -p cadvm # the binary crate (the MCP server)
Ownership verification: the cadvm crate’s README already carries the visible
marker mcp-name: io.github.AdeMBCH/cadvm, which crates.io serves and the
registry checks. Keep it intact.
2. Keep versions in sync
The release tag, the workspace version, the crate on crates.io, and the two
version fields in server.json must all match (e.g. 0.1.1).
3. Publish to the registry
mcp-publisher login github # browser OAuth → grants io.github.AdeMBCH/*
mcp-publisher publish # reads ./server.json
Verify it is listed:
curl -s "https://registry.modelcontextprotocol.io/v0/servers?search=cadvm" | jq .
Optional: publish from CI (OIDC, no browser)
In a release workflow, authenticate with GitHub OIDC instead of a browser:
permissions:
id-token: write
contents: read
steps:
- run: |
curl -fsSL https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_linux_amd64.tar.gz | tar -xz
./mcp-publisher login github-oidc
./mcp-publisher publish
(Run this after the crates are published to crates.io for that version.)
Notes
- Once a version is published it cannot be re-published; bump the version.
- The registry currently restricts cargo packages to
https://crates.io. - A no-toolchain MCPB path (prebuilt binary from GitHub Releases) is an alternative for end users without Rust — a possible future addition.
License
cadvm is released under the Prosperity Public License 3.0.0 — a source-available license.
In plain terms:
- ✅ Free for noncommercial use — personal projects, research, education, nonprofits, evaluation.
- ✅ Reuse & modify — you may build on cadvm as a base and share your changes for noncommercial purposes.
- ⏳ Commercial use gets a 30-day trial; beyond that it requires a commercial license from the maintainer.
- 🚫 No reselling cadvm (or a derivative) as a commercial product without a commercial license.
This is source-available, not OSI “open source,” because it restricts
commercial use. The full, authoritative text is in
LICENSE.
This page is a plain-language summary, not legal advice. For commercial licensing, contact the maintainer.