Visual diff, merge, and canonical ordering tools for KiCad
Every time KiCad saves a .kicad_pcb or .kicad_sch file, it shuffles the internal element order (due to non-deterministic hash map iteration in KiCad's C++ code). This means:
- Git diffs are useless — a single component move generates thousands of meaningless line changes
- Merging is painful — two engineers editing different parts of the same board get massive conflicts
- Code review is impossible — you can't tell what actually changed
There's an open KiCad bug for this since 2021 with no fix in sight.
Sort KiCad files into a canonical, deterministic order. Run before every commit — diffs become readable.
# Normalize files in-place
kicad-unshuffle *.kicad_pcb *.kicad_sch *.kicad_sym
# Check mode for CI (exits 1 if files would change)
kicad-unshuffle --check *.kicad_pcb
# Pipe mode for git filter-repo
kicad-unshuffle --stdin --stdout < board.kicad_pcb > sorted.kicad_pcbSee exactly what changed between two versions of a schematic. Identical elements appear white, changes appear in red (version A) and cyan (version B). Pure Rust SVG renderer — no KiCad installation required.
# Render a schematic to SVG
kicad-unshuffle render-svg board.kicad_sch -o board.svg
# Red/cyan diff overlay
kicad-unshuffle diff-svg version_a.kicad_sch version_b.kicad_sch -o diff.svgA Tauri desktop app that lets you open a git repo, pick two branches or commits, and visually review every changed KiCad file.
Features:
- Git repository browser — open a repo, pick two branches/commits, filter by hash or message
- Side-by-side view — KiCanvas renders both versions with click-to-zoom on any element
- SVG overlay — red/cyan diff rendered in pure Rust (vector, infinite zoom, no lag)
- Text diff — syntax-highlighted S-expression diff showing exactly which properties changed
- Clickable element list — every modified symbol, wire, label listed with click-to-navigate
Compare and merge KiCad files at the element level — matching by UUID, not by line number.
# Compare two branches — shows which KiCad files differ
kicad-unshuffle repo-merge --repo /path/to/project main feature-branch
# Git merge driver (register in .gitattributes)
kicad-unshuffle merge-driver %O %A %B| File Type | Extension | Normalize | Diff/Merge | SVG Render |
|---|---|---|---|---|
| PCB Layout | .kicad_pcb |
✅ | ✅ | Planned |
| Schematic | .kicad_sch |
✅ | ✅ | ✅ |
| Symbol Library | .kicad_sym |
✅ | ✅ | — |
| Footprint Library | .kicad_mod |
✅ | ✅ | — |
| Project File | .kicad_pro |
✅ | — | — |
| Design Rules | .kicad_dru |
✅ | — | — |
# CLI tool only
cargo install --path crates/cli
# Desktop app (requires Node.js for the Vue frontend)
cd ui
npm install
cargo tauri build- Rust 1.77+
- Node.js 18+ (for the desktop app only)
- KiCad is NOT required — the tool parses and renders files directly
Tested on real-world hardware projects (32-34MB PCB files, 18 hierarchical schematic sheets):
| Operation | File Size | Time |
|---|---|---|
| Normalize PCB | 32 MB | 0.83s |
| Normalize PCB | 34 MB | 0.91s |
| Merge PCB (33MB × 3 versions) | 99 MB total | 2.6s |
| SVG render schematic | 72 KB | instant |
| SVG diff overlay | 72 + 131 KB | instant |
All operations are idempotent — running twice produces byte-identical output.
Parses KiCad's S-expression format, sorts elements using configurable deterministic keys (coordinates for PCBs, reference designators for schematics), and writes back in KiCad-compatible format. The sort strategy is configurable via TOML — choose coordinate-based sorting for merge-friendly PCB diffs, or refdes-based sorting for readable schematic diffs.
Matches elements across versions by UUID. Classifies each as unchanged, added, deleted, modified, or conflicting. Non-overlapping changes are auto-resolved. Real conflicts (both versions modified the same element differently) are flagged for manual resolution in the desktop app.
Pure Rust renderer using the svg crate. Converts .kicad_sch S-expressions directly to SVG: wires, junctions, labels, symbols (with full lib_symbol rendering — rectangles, polylines, circles, arcs, pins), hierarchical sheets, bus entries, and text with alignment. For diff overlay, version A renders in red, version B in cyan, composited with mix-blend-mode: screen — identical geometry cancels to white.
Native git access via the git2 crate — reads files directly from any commit without checkout. The repo-merge command finds the merge base, extracts all three versions of each changed KiCad file, normalizes them, and runs the semantic diff.
kicad-unshuffle/
├── crates/
│ ├── core/ # Rust library (zero UI dependencies)
│ │ ├── sexpr/ # S-expression parser and writer
│ │ ├── sort/ # Configurable canonical sort engine
│ │ ├── merge/ # Semantic diff and merge (identity, diff, resolver)
│ │ ├── render/ # SVG renderer (schematic → SVG with tint)
│ │ └── repo/ # Git repository operations (via git2)
│ └── cli/ # Command-line interface
├── ui/
│ ├── src/ # Vue 3 + TypeScript frontend
│ ├── src-tauri/ # Tauri 2 Rust backend
│ └── public/ # KiCanvas bundle for interactive rendering
└── kicanvas-fork/ # Modified KiCanvas with tint shader support
This tool was built to solve a real collaboration problem on a hardware project. If you're working with KiCad and git, we'd love feedback and contributions.
MIT
