Two new tools for managing hierarchical schematic connections:
- add_schematic_hierarchical_label: create sheet interface ports on
sub-sheet schematics. These are the sub-sheet side of hierarchical
connections, linking to sheet pins on the parent.
- add_sheet_pin: add pins to sheet symbol blocks on the parent
schematic. Targets the correct sheet by matching the Sheetname
property. The pinName must match a hierarchical_label in the
sub-sheet.
Both tools use text-based S-expression insertion (not sexpdata
round-trip) to preserve KiCad's native file formatting. Labels
include proper justification based on orientation: left-justify for
rightward labels (0°), right-justify for leftward labels (180°).
Wire manager additions:
- _find_insertion_point(): locates sheet_instances block or final paren
- _text_insert(): inserts formatted S-expression text at the right position
- _make_hierarchical_label_text(): generates hierarchical_label S-expression
- _make_sheet_pin_text(): generates sheet pin S-expression
- WireManager.add_hierarchical_label(): static method for label insertion
- WireManager.add_sheet_pin(): static method for pin insertion into
named sheet blocks
12 unit tests covering insertion, orientation/justification mapping,
parameter validation, multi-sheet targeting, and error handling.
KiCad ships Python 3.9 on macOS (both v9 and v10), and `pcbnew.so` is
compiled against that interpreter. The previous `>=3.10` floor was
unreachable on macOS and docs referenced nonexistent
`python3.11/site-packages` paths. Lower the floor and correct the docs;
no source changes needed — all 49 files in `python/` already compile
clean on 3.9.
Also add `.venv/` to `.gitignore` (dotted form used by `python -m venv`).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Two issues with run_erc on KiCad 9:
1. kicad-cli returns non-zero exit code when ERC violations exist.
The handler treated this as a command failure and returned early
with success=false, even though valid JSON output was produced.
Fix: check for output file existence instead of exit code.
2. KiCad 9 nests violations under sheets[].violations instead of
(or in addition to) the top-level violations[] array used by
KiCad 8. The handler only read the top-level array, reporting
0 violations on schematics with sub-sheets.
Fix: iterate sheets[] and collect all nested violations.
Both fixes are backward-compatible with KiCad 8.
6 unit tests added covering non-zero exit codes, KiCad 8 top-level
violations, KiCad 9 sheets[] nesting, mixed structures, zero
violations, and missing output files.
export_netlist was returning "Unknown command" because no Python handler
existed. generate_netlist was timing out (30s) due to an O(nets × components
× pins) wire-graph algorithm with a new PinLocator instantiated per net.
Both handlers now delegate to `kicad-cli sch export netlist`:
- export_netlist: new handler; writes KiCad XML / Spice / Cadstar / OrcadPCB2
to the caller-supplied outputPath. Added schematicPath parameter to the TS
tool definition (was absent, making file export impossible).
- generate_netlist: replaces the slow wire-graph with kicad-cli + XML parse;
returns the same {components, nets} JSON the TS handler already expected.
Also adds _find_kicad_cli_static() so both handlers share CLI discovery
without depending on ExportCommands (which requires a loaded pcbnew board).
Cleaned up generate_netlist schema in tool_schemas.py (removed outputPath and
format fields the handler never used), updated MCP tool descriptions and
SCHEMATIC_TOOLS_REFERENCE.md to clearly distinguish the two tools.
26 unit tests added covering parameter validation, subprocess mocking,
format mapping, XML→JSON parsing, and error/timeout propagation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Half of all valid KiCAD schematic pin positions are on the 50-mil
(1.27mm) grid but not the 100-mil (2.54mm) grid — e.g. 26.67mm = 21 ×
1.27mm. Snapping to 2.54mm displaced those coordinates by 1.27mm,
moving labels off their pins and increasing floating-label count.
KiCAD source confirms: DEFAULT_CONNECTION_GRID_MILS = 50 and the ERC
off-grid check uses exact integer modulo against this value, so any
displacement breaks connectivity unconditionally.
Also update the kicad-source absolute path in CLAUDE.md.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Four tools added in this branch had no docs coverage. Add entries to
SCHEMATIC_TOOLS_REFERENCE.md with parameter tables, response field
tables, and usage notes. Update section tool counts (Net Analysis 4→5,
Validation 3→6).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
get_pin_net was a superset of get_wire_connections with the same
coordinate-based flood-fill but two extra response fields (net, query_point)
and a reference+pin input mode. Having both tools confused LLM tool selection.
get_wire_connections now:
- Returns net (label name or null) and query_point in all response paths
- Accepts reference+pin input in addition to x/y coordinates,
resolving the pin endpoint via PinLocator internally
get_pin_net tool, handler, schema, TS registration, and tests removed.
test_wire_connectivity.py updated with coverage for all new behaviour.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Answers "what net is pin X of component Y on?" without requiring
callers to triangulate from list_schematic_nets or know a wire
coordinate first.
Accepts either {reference, pin} (resolved via PinLocator) or {x, y}
coordinate. Returns net label name (or null for unnamed nets), all
connected pins, wire segments, and the resolved query point.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a new MCP tool that snaps wire endpoints, junction positions, and
net label coordinates to the nearest grid point (default 2.54 mm). Off-grid
coordinates cause wires that appear visually connected to fail ERC checks
because KiCAD uses exact IU integer matching internally; this tool eliminates
that class of error before running ERC.
- python/commands/schematic_snap.py: core snap logic with in-place sexp mutation
- python/kicad_interface.py: route + handler
- python/schemas/tool_schemas.py: JSON schema (gridSize, elements params)
- src/tools/schematic.ts: TypeScript MCP tool registration
- tests/test_snap_to_grid.py: 20 unit + integration tests (all passing)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Detects wire segments with at least one dangling endpoint — an endpoint
not connected to a component pin, net label, or another wire. These
cause ERC 'wire end unconnected' violations and are a common symptom of
incomplete routing or stray stub wires.
Algorithm uses exact KiCad IU (10 000 IU/mm) coordinate matching,
consistent with wire_connectivity.py:
1. Build an endpoint-frequency map for all wires (IU precision)
2. Collect anchored IU points: component pins (via PinLocator),
net labels / global_labels, power symbol pins
(via _parse_virtual_connections)
3. An endpoint is dangling when it is touched by exactly one wire AND
is not an anchored point; the containing wire is reported
Does not require the KiCad UI to be running.
Changes:
python/commands/schematic_analysis.py — find_orphaned_wires() function
python/kicad_interface.py — handler + route registration
python/schemas/tool_schemas.py — MCP schema entry
src/tools/schematic.ts — TypeScript server.tool() call
tests/test_schematic_analysis.py — 7 integration tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- add_schematic_net_label: document new optional componentRef/pinNumber
snap-to-pin mode, labelType/orientation params, response fields
(actual_position, snapped_to_pin), and updated position to optional
- connect_to_net: document new response fields (pin_location,
label_location, wire_stub)
- get_schematic_pin_locations: update cross-reference to reflect that
direct pin snapping is now available in add_schematic_net_label
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
add_schematic_net_label now accepts optional componentRef + pinNumber to
snap the label directly to the exact pin endpoint via PinLocator, removing
all approximation risk. The response always includes actual_position and,
when snapping was used, snapped_to_pin — so the caller gets confirmation
of exactly where the label landed.
connect_to_net return type changed from bool to Dict, returning
pin_location, label_location, and wire_stub on success so agents no
longer need a separate verification call to confirm placement.
connect_passthrough updated to check result.get("success") against the
new dict return. tool_schemas.py and schematic.ts updated to match
(position is now optional, componentRef/pinNumber/labelType/orientation
added, connect_to_net schema field names corrected).
17 new unit tests in tests/test_net_label_pin_snapping.py.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Move all Python test files from python/tests/ to the top-level tests/
directory to match the project's CONTRIBUTING.md guidelines. Update
sys.path inserts and template path references to reflect the new
location.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fix incorrect default Claude Desktop config path — macOS uses
~/Library/Application Support/Claude/claude_desktop_config.json,
not ~/.config/Claude/claude_desktop_config.json.
Capture stderr in merge_config subshell (2>&1) so Python tracebacks
and interpreter errors are surfaced instead of silently dropped when
the command substitution fails under set -e.
Update README.md and docs/PLATFORM_GUIDE.md to reflect the corrected
macOS config path, and split the combined Linux/macOS config location
reference into separate per-platform entries.
Add trailing newline at the end.
Add setup-macos.sh, a shell script that automates Claude Desktop MCP
configuration on macOS. The script detects KiCad's bundled Python,
resolves PYTHONPATH, generates the correct MCP server config, and
safely merges it into the existing Claude Desktop configuration with
backup support. Supports --verify, --dry-run, and --apply modes.
Update README.md with documentation for the automated setup workflow,
including usage examples, parameter reference, and post-setup steps.
Extend docs/PLATFORM_GUIDE.md with macOS-specific sections covering
installation, path handling, Python environment, troubleshooting,
best practices, cross-platform migration, and support resources.
Update docs/STATUS_SUMMARY.md to reflect the new macOS automation
capabilities and bump the last-updated date.
Add type annotations to all previously untyped functions and remove 9
suppressed error codes (call-arg, assignment, return-value, operator,
has-type, dict-item, misc, list-item, annotation-unchecked) by fixing
the underlying type issues.
Add [[tool.mypy.overrides]] with ignore_missing_imports for KiCAD-specific
modules (pcbnew, sexpdata, skip, cairosvg, kipy, PIL) so the pre-commit
mypy hook passes in its isolated venv. Add types-requests and pytest to
additional_dependencies in .pre-commit-config.yaml.
Also fixes several real bugs uncovered by stricter checks: incorrect static
calls to instance methods in swig_backend, wrong return type on get_size,
missing value param in BoardAPI.place_component, variable shadowing in
kicad_process.py, unqualified LibraryManager reference in kicad_interface,
and missing top-level Path import.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
With --system-site-packages, mypy can resolve pcbnew, skip, and sexpdata
directly from the system/venv, so per-module ignore_missing_imports
overrides are no longer needed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
These packages are not imported anywhere in the codebase, so their
ignore_missing_imports overrides were unnecessary.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove global ignore_missing_imports and the import-untyped disable,
replacing them with targeted per-package overrides for third-party
libraries that lack stubs (pcbnew, skip, sexpdata, cairosvg, kipy,
schematic). Also add python/ to mypy_path so internal modules resolve.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add list_schematic_nets, list_schematic_labels, and get_schematic_view
to the longRunningCommands list so they use the 10-minute timeout
instead of the default 30 seconds. These commands regularly exceed
the 30s limit on larger schematic files.
The TS<->Python communication channel uses stdout for JSON responses.
pcbnew's C++ SWIG layer can write warnings and diagnostics directly to
C-level stdout (fd 1), corrupting the JSON framing. The TS parser then
never sees valid JSON and the command times out after 30 seconds.
Three changes fix this:
1. Python stdout redirect: In main(), save the original stdout fd for
exclusive JSON response use, then redirect fd 1 to stderr so all
pcbnew C++ output goes to logs instead of the response pipe.
2. Robust TS JSON parser: tryParseResponse() now uses newline-delimited
parsing as a fallback. The Python side writes single-line JSON
terminated by \n; the parser uses this as the completion signal
instead of brace-matching, which prevents premature resolution of
truncated chunked responses. Non-JSON preamble lines are logged
and stripped.
3. Fix stray print() calls: Converted print() to logger in
component_schematic.py and library_schematic.py so they don't
leak to stdout during normal operations.
Also adds sync_schematic_to_board to the longRunningCommands list for
an appropriate timeout value.