feat: add schematic analysis tools (read-only)

Add five new read-only schematic analysis MCP tools:
- get_schematic_view_region: export cropped schematic region as PNG/SVG
- find_unconnected_pins: list pins with no wire/label/power connection
- find_overlapping_elements: detect duplicate symbols, stacked labels, collinear wire overlaps
- get_elements_in_region: list all symbols/wires/labels in a bounding box
- check_wire_collisions: detect wires passing through component bodies

Includes Python handler dispatch, tool schemas, TypeScript server bindings,
the schematic_analysis command module, and a full test suite (28 tests passing).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Eugene Mikhantyev
2026-03-14 20:39:49 +00:00
parent 445c3ac1ee
commit 764b8db3d3
7 changed files with 1575 additions and 26 deletions

View File

@@ -1,26 +1,41 @@
"""
Pytest configuration for python/tests.
Sets up sys.path so that the python/ package root is importable without
installing the project, and provides shared fixtures.
"""
import sys
from pathlib import Path
# Make the python/ package root importable
PYTHON_ROOT = Path(__file__).parent.parent
if str(PYTHON_ROOT) not in sys.path:
sys.path.insert(0, str(PYTHON_ROOT))
# Stub out heavy KiCAD C-extension modules so tests can run without a real
# KiCAD installation. Extend this list whenever a new import fails.
import types
from unittest.mock import MagicMock
# Use MagicMock so any attribute access (e.g. pcbnew.BOARD, pcbnew.LoadBoard)
# returns another MagicMock rather than raising AttributeError.
for _stub_name in ("pcbnew", "skip"):
if _stub_name not in sys.modules:
_m = MagicMock(spec_set=None)
_m.__name__ = _stub_name
sys.modules[_stub_name] = _m
"""
Test configuration for python/tests.
Sets up sys.modules stubs for heavy KiCAD modules (pcbnew, skip) before any
test module can trigger their import, preventing crashes on systems where the
real KiCAD environment is not fully initialised for testing.
"""
import sys
import types
from unittest.mock import MagicMock
# ---------------------------------------------------------------------------
# pcbnew stub — kicad_interface.py accesses pcbnew.__file__ and
# pcbnew.GetBuildVersion() at module level. Use MagicMock so that any
# attribute access (pcbnew.BOARD, pcbnew.PCB_TRACK, …) returns a mock
# rather than raising AttributeError.
# ---------------------------------------------------------------------------
_pcbnew = MagicMock(name="pcbnew")
_pcbnew.__file__ = "/fake/pcbnew.cpython-313-x86_64-linux-gnu.so"
_pcbnew.__name__ = "pcbnew"
_pcbnew.__spec__ = None
_pcbnew.GetBuildVersion.return_value = "9.0.0-stub"
sys.modules["pcbnew"] = _pcbnew
# ---------------------------------------------------------------------------
# Stub: skip (kicad-skip — use real module if available, stub otherwise)
# ---------------------------------------------------------------------------
try:
import skip as _skip_test # noqa: F401 — try importing real skip
except ImportError:
skip_mod = types.ModuleType("skip")
class _FakeSchematic:
"""Minimal stand-in for skip.Schematic used in PinLocator cache."""
def __init__(self, path: str):
self.path = path
self.symbol = []
skip_mod.Schematic = _FakeSchematic # type: ignore[attr-defined]
sys.modules["skip"] = skip_mod