feat(routing): add add_gnd_stitching_vias MCP tool with all-layer collision (#191)

Drop GND stitching vias across the board with collision checking
against every non-GND segment, via, and pad on every copper layer.
PTH vias penetrate the full stackup, so an F.Cu-only check (the most
common shortcut) silently creates shorts on inner / B.Cu copper —
this implementation explicitly walks all layers.

  grid          Regular grid across the board interior. Default
                spacing 5mm.

  around_refs   Densify around specified footprints (e.g. MCUs,
                switching regulators, RF parts). Configurable
                density via densifyRadius.

  in_zones      Restrict placements to candidates inside the filled
                polygons of GND copper zones, so each new via lands
                on copper that's already a GND equipotential.
                Recommended on boards where the GND zone is fragmented:
                these vias actually stitch real polygons rather than
                floating on silkscreen.

All three strategies use the same collision check + intra-call
clump-prevention, so passing `["grid", "around_refs", "in_zones"]`
is a safe kitchen-sink configuration.

  - Auto-detect GND net (tries GND / GROUND / VSS / /GND in order)
    OR explicit `gndNet` parameter.
  - Per-via geometry control: viaSize, viaDrill, clearance.
  - edgeMargin: keep-out distance from board edge.
  - maxVias: cap on total placements (useful for incremental work).
  - dryRun: return placements without modifying the board — for
    previewing before committing.
  - Validates viaDrill < viaSize, rejects unknown strategy names,
    surfaces clear errors when GND net can't be resolved or the
    board outline is missing.

Approach ported from morningfire-pcb-automation
(https://github.com/NiNjA-CodE/morningfire-pcb-automation,
scripts/ground/add_gnd_vias.py). The original parses the PCB text
with regex and writes vias by string concatenation; this port reads
obstacles via the pcbnew API (handles rotated footprints, integrates
with the live in-memory board so two sequential calls see each
other's placements, picks up net codes from the loaded board) and
adds the in_zones strategy, the maxVias cap, and dry-run mode.

Credit is in the docstring, the TypeScript wrapper comment, the MCP
tool description (visible to clients), and the CHANGELOG entry.

tests/test_add_gnd_stitching_vias.py — 18 cases, all passing.
Uses mocked pcbnew objects so the suite runs under both the conftest
stub and a real pcbnew install.

  - grid strategy fills empty board with correct count
  - collision blocks via near a signal track (with extent assertion)
  - GND-net obstacles are correctly ignored
  - around_refs densifies near footprints with bounded extent
  - in_zones rejects candidates outside HitTestFilledArea
  - dryRun does NOT call board.Add
  - actual run calls board.Add per placement
  - maxVias caps total placements
  - intra-call clump prevention (asserts pairwise distance)
  - viaDrill >= viaSize is rejected
  - unknown strategy name is rejected
  - missing GND net returns clear error payload
  - no board loaded returns clear error
  - named GND net (e.g. VSS) is honoured even when GND also exists
  - direct unit tests for _point_to_segment_distance_nm helper

Real-board smoke test on TuneForge_TF001 (4-layer, 44 footprints):
  - GND net auto-detected
  - grid spacing 4mm: 141 placements, 129 blocked by collision
  - grid + in_zones: 140 placed, 15 rejected by zone membership,
    115 blocked by collision

  python/commands/routing.py         (+impl, ~370 LOC)
  python/kicad_interface.py          (+handler registration)
  python/schemas/tool_schemas.py     (+MCP schema)
  src/tools/routing.ts               (+TypeScript surface, builds clean)
  tests/test_add_gnd_stitching_vias.py (+18 tests)
  CHANGELOG.md                       (+Unreleased -> New MCP Tools)
This commit is contained in:
NiNjA-CodE
2026-05-19 19:17:25 -06:00
committed by GitHub
parent f03a74a93f
commit 76e644e4ef
6 changed files with 1041 additions and 0 deletions

View File

@@ -42,6 +42,42 @@ All notable changes to the KiCAD MCP Server project are documented here.
### New MCP Tools
- `add_gnd_stitching_vias` — Drop GND stitching vias across the board with
collision checking against every non-GND segment, via, and pad on every
copper layer. PTH vias penetrate the full stackup, so an F.Cu-only check
(the most common shortcut) silently creates shorts on inner / B.Cu
copper — this implementation explicitly walks all layers.
Combines three placement strategies, freely composable:
- `grid` — regular grid across the board interior.
- `around_refs` — densify around named footprints (good for tucking
extra ground under MCUs, switching regulators, or RF parts).
- `in_zones` — restrict candidates to points inside the filled
polygons of GND copper zones, so each new via actually stitches
real ground polygons together rather than floating on silkscreen.
Also supports per-via geometry control (`viaSize`, `viaDrill`,
`clearance`, `edgeMargin`), an `maxVias` cap for incremental work,
auto-detection of the GND net (tries `GND` / `GROUND` / `VSS` /
`/GND`), and a `dryRun` mode that returns the placements that
*would* be made without modifying the board — useful for previewing
before committing.
Returns `{ placed: [{x, y, unit}, ...], summary: {placed_count,
candidates_evaluated, skipped_by_zone_membership,
skipped_by_collision, ...} }`.
Approach ported from
[morningfire-pcb-automation](https://github.com/NiNjA-CodE/morningfire-pcb-automation)
(`scripts/ground/add_gnd_vias.py`). The original parses the PCB
text with regex and writes new vias by string concatenation; this
port reads obstacles via the pcbnew API so it handles rotated
footprints correctly, integrates with the in-memory board (two
sequential calls see each other's placements), picks up net codes
from the live board, and adds the `in_zones` strategy + the
`maxVias` cap + dry-run.
- `check_courtyard_overlaps` — Detect courtyard overlaps between footprints
and (optionally) flag courtyards that extend past the board outline.
Returns overlap pairs with intersection extents (mm), per-component