feat(component): add check_courtyard_overlaps MCP tool (#189)
Detects courtyard overlaps between footprints and flags courtyards that
extend past the board outline. Returns overlap pairs with intersection
extents (mm), per-component boundary violations, and a placement summary.
The killer feature for AI-driven workflows is the `positions` parameter,
which accepts hypothetical placements `{ref: [x, y]}` or
`{ref: [x, y, rotation_degrees]}`. The tool evaluates the proposed
placement WITHOUT writing to the board file — so an AI agent can validate
a move_component / place_component before committing it, instead of the
current loop of write -> run DRC -> parse violations -> revert.
## Implementation
- Uses the real courtyard polygons from pcbnew (`fp.GetCourtyard(F_CrtYd)`
or B_CrtYd) for accurate AABBs even on custom and rotated footprints.
- Falls back to `fp.GetBoundingBox()` when no F/B.Courtyard polygon is
present.
- For virtual rotation, rotates the four AABB corners and re-axis-aligns.
Conservative: the rotated-AABB is always >= the rotated-polygon, so
overlap reports are never false-negatives (may be marginally
over-cautious on diagonal rectangles, which is the right error bias
for a placement validator).
- Optional `margin` parameter expands every courtyard by N mm — useful
for enforcing a manufacturing keepout wider than the symbol's
declared courtyard.
## Attribution
The approach is ported from morningfire-pcb-automation
(https://github.com/NiNjA-CodE/morningfire-pcb-automation), specifically
`scripts/placement/check_overlaps.py`. The upstream uses a static
per-footprint-type courtyard lookup table; this implementation reads
the real polygons from pcbnew so it works on any footprint without
maintaining a table. Attribution is in the function docstring, the
TypeScript wrapper, the tool's description (visible to MCP clients),
and the CHANGELOG entry.
## Tests
12 pytest cases in tests/test_check_courtyard_overlaps.py, all passing:
- No overlaps when spaced; overlap detected on intersect
- Margin pushes borderline pairs into overlap
- `refs` filter restricts the check
- Boundary violations are flagged; `include_boundary=false` suppresses
- Virtual position does not mutate the footprint (asserts
`SetPosition` is never called)
- Virtual rotation swaps a tall-narrow courtyard's x/y extents
- No-board-loaded returns clean error payload
- Bad position spec (wrong arity) returns clean error payload
- GetCourtyard() OutlineCount=0 -> fallback to GetBoundingBox()
- `board_outline` override replaces the Edge.Cuts bbox
Tests use mocked pcbnew objects so they run under both the conftest stub
and a real pcbnew install. Real-board smoke test on a 44-footprint
production board succeeds: 1 known overlap detected (SW1<->SW2), 0
boundary violations, virtual placement test reports 6 expected overlaps.
## Files touched
- python/commands/component.py (impl + helpers)
- python/kicad_interface.py (tool registration)
- python/schemas/tool_schemas.py (MCP schema entry)
- src/tools/component.ts (TypeScript surface, builds clean)
- tests/test_check_courtyard_overlaps.py (12 cases)
- CHANGELOG.md (Unreleased -> New MCP Tools)
This commit is contained in:
@@ -717,6 +717,88 @@ COMPONENT_TOOLS = [
|
||||
"required": ["references", "direction"],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "check_courtyard_overlaps",
|
||||
"title": "Check Courtyard Overlaps",
|
||||
"description": (
|
||||
"Detects courtyard overlaps between footprints and (optionally) flags "
|
||||
"footprints whose courtyard extends past the board outline. "
|
||||
"Returns overlap pairs with intersection extents and per-component "
|
||||
"boundary violations, both in mm. Accepts a 'positions' dict to "
|
||||
"evaluate a HYPOTHETICAL placement without modifying the board — "
|
||||
"use this before committing a move_component / place_component call "
|
||||
"to know if it will trigger DRC. "
|
||||
"Approach ported from morningfire-pcb-automation "
|
||||
"(https://github.com/NiNjA-CodE/morningfire-pcb-automation, "
|
||||
"scripts/placement/check_overlaps.py); this version reads real "
|
||||
"courtyard polygons from the board (not a static lookup table) and "
|
||||
"supports virtual placement + rotation + clearance margin."
|
||||
),
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"positions": {
|
||||
"type": "object",
|
||||
"description": (
|
||||
"Virtual placements: map of reference designator to "
|
||||
"[x, y] or [x, y, rotation_degrees] in mm. Each listed "
|
||||
"ref is checked AS IF it were at the given coordinates. "
|
||||
"Unspecified refs use their current board position."
|
||||
),
|
||||
"additionalProperties": {
|
||||
"type": "array",
|
||||
"items": {"type": "number"},
|
||||
"minItems": 2,
|
||||
"maxItems": 3,
|
||||
},
|
||||
},
|
||||
"refs": {
|
||||
"type": "array",
|
||||
"description": (
|
||||
"Limit the check to these refs (default: every "
|
||||
"footprint on the board)."
|
||||
),
|
||||
"items": {"type": "string"},
|
||||
},
|
||||
"margin": {
|
||||
"type": "number",
|
||||
"description": (
|
||||
"Extra clearance in mm added around every courtyard "
|
||||
"(default 0). Useful to enforce a manufacturing keepout "
|
||||
"wider than the symbol's declared courtyard."
|
||||
),
|
||||
"default": 0,
|
||||
},
|
||||
"include_boundary": {
|
||||
"type": "boolean",
|
||||
"description": (
|
||||
"Also flag courtyards that extend past the board outline "
|
||||
"(default true)."
|
||||
),
|
||||
"default": True,
|
||||
},
|
||||
"board_outline": {
|
||||
"type": "object",
|
||||
"description": (
|
||||
"Optional override for the board outline bbox. Default: "
|
||||
"derived from Edge.Cuts."
|
||||
),
|
||||
"properties": {
|
||||
"x1": {"type": "number"},
|
||||
"y1": {"type": "number"},
|
||||
"x2": {"type": "number"},
|
||||
"y2": {"type": "number"},
|
||||
"unit": {
|
||||
"type": "string",
|
||||
"enum": ["mm", "inch"],
|
||||
"default": "mm",
|
||||
},
|
||||
},
|
||||
"required": ["x1", "y1", "x2", "y2"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "duplicate_component",
|
||||
"title": "Duplicate Component",
|
||||
|
||||
Reference in New Issue
Block a user