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>
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 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>
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>
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.
When moving a schematic component whose pins directly touch pins of
stationary components (no wire segment, just pin-to-pin contact),
synthesize bridge wires to preserve the electrical connection after
the move. Also fixes duplicate-pin-position collision in old_to_new
map and updates symbol reference assignment to use setAllReferences.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When moving a schematic component, connected wires are stretched/shifted
to follow the component (like KiCAD's drag behaviour), preserving
connectivity instead of leaving dangling wire stubs.
Also fixes property labels (value, reference, etc.) so they shift with
the symbol rather than staying at their original positions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add isort configuration (profile=black, line_length=100) to pyproject.toml,
add isort pre-commit hook, and auto-sort imports across all Python source files.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add [tool.black] config to pyproject.toml and Black hook to
.pre-commit-config.yaml (rev 26.3.1), then auto-format all Python
source and test files with line-length=100, target-version=py310.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Rename add_schematic_connection → add_schematic_wire with waypoints[] parameter
- Add snapToPins (default true) to snap wire endpoints to nearest pin
- Expose add_schematic_junction as an MCP tool
- Break existing wires at new wire endpoints for T-junction support
- Remove orphaned add_connection / add_wire / get_pin_location from ConnectionManager
- Update tool registry to reflect renamed schematic tools in TS layer
- Add 76 tests for wire/junction handler dispatch, schema validation, and WireManager corner cases
- Apply Black and Prettier formatting to changed files
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Move wire connectivity logic from _handle_get_wire_connections into
commands/wire_connectivity.py. Use KiCad's internal integer unit system
(10,000 IU/mm) with exact coordinate matching instead of tolerance-based
float comparison, mirroring how KiCad itself determines connectivity.
Key improvements:
- Exact integer matching for wire endpoints (O(1) dict lookup vs O(n) grid scan)
- Junction support for T-connections
- Multi-unit symbol support (removed incorrect processed_refs dedup)
- Single public API: get_wire_connections()
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace runtime spatial-index queries during BFS with a pre-compiled
adjacency list for O(1) edge traversal. Also fix potential UnboundLocalError
for `ref` in the pin-checking exception handler and simplify validation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add +1 safety margin to grid_radius to handle banker's rounding at cell
boundaries in the spatial index
- Move symbol property guards inside per-symbol try/except to prevent
AttributeError from aborting all pin processing
- Replace O(n) connected_points scan for pin matching with spatial index
lookup (_frontier_has_neighbour), consistent with flood-fill approach
- Wrap float(x)/float(y) conversion with clear user-facing error message
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix critical bug: use pin_data[0]/[1] instead of pin_data["x"]/["y"]
(get_all_symbol_pins returns List[float], not dict)
- Use index-based wire tracking to avoid fragile float list equality
- Check all polyline points (not just endpoints) during flood-fill
- Add spatial index (0.05mm grid) to replace O(n²) frontier scan
- Skip already-processed refs to avoid redundant calls for multi-unit symbols
- Include wires_out in early return when schematic has no symbols
- Add get_wire_connections schema entry to tool_schemas.py
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Given a single (x,y) coordinate on the schematic, flood-fills through all
connected wire segments and returns every component pin reachable on that net,
plus the full list of wire segments with their start/end coordinates.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
KiCad's native ERC already checks for unconnected pins with better
accuracy (hierarchical sheets, bus connections, custom rules). Remove
the reimplemented version and its dead helper _parse_no_connects.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Eliminate repeated file parsing by extracting _extract_lib_symbols helper
that walks already-parsed sexp_data once instead of re-reading the file
per symbol via PinLocator. Support diagonal wire overlap detection using
cross-product parallelism and 1D projection. Fix wire region inclusion to
use AABB intersection for pass-through wires. Normalize view region
coordinates. Clarify tolerance docstrings across Python, TS, and schema.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wires that start at a component pin but continue through the body were
incorrectly suppressed as "valid connections." Now nudges the pin endpoint
toward the other end and re-tests intersection — if the shortened segment
still hits the bbox, the wire passes through and is flagged.
Renamed the tool from check_wire_collisions to find_wires_crossing_symbols
across all layers (Python, handler, schema, TypeScript) to clarify that it
finds wires crossing over component symbols, which is unacceptable in
schematics.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Use a temp directory (not file) for kicad-cli svg output, which expects a directory path
- Clean up temp dir with shutil.rmtree instead of individual file unlinks
- Fix viewBox cropping: KiCad schematic SVGs use mm directly, not mils (removed erroneous 39.3701 multiplier)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
4 new MCP tools: autoroute (full DSN→Freerouting→SES pipeline),
export_dsn, import_ses, check_freerouting. Requires Java 11+ and
freerouting.jar. Includes 21 test cases and README usage examples.
- Add python/tests/conftest.py with MagicMock stubs for pcbnew/skip
- Add python/tests/test_schematic_tools.py with 29 tests covering
WireManager.delete_wire, WireManager.delete_label (unit + integration),
and parameter validation for all 11 new _handle_* methods
- Apply black formatting to component_schematic.py, wire_manager.py,
and kicad_interface.py
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The handler used a line-by-line regex requiring (symbol and (lib_id on
the same line, but KiCAD's file writer places them on separate lines.
Replace with the content-string approach (already used by edit handler)
so \s+ matches across newlines. Add regression tests covering inline,
multi-line, and power symbol (#PWR) cases.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New `get_schematic_component` MCP tool returns component position and
all field values with their label (at x/y/angle) positions
- Extends `edit_schematic_component` with optional `fieldPositions` dict
so callers can reposition Reference/Value/etc. labels in one call
- Adds 18 tests (6 unit, 12 integration) covering parsing, round-trips,
and edge cases
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
delete_schematic_net_label was using kicad-skip's write() which silently
discards in-memory _elements mutations. Replaced with WireManager.delete_label()
that uses the same sexpdata round-trip approach already used by delete_schematic_wire.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
kicad-skip's write() serializes from original parsed data, silently
discarding in-memory _elements mutations. Switch to the same sexpdata
approach used by add_wire: parse, find matching wire, delete the entry,
write back. Also adds WireManager.delete_wire() static method.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ElementCollection in kicad-skip doesn't implement remove(), causing
'WireCollection object has no attribute remove' errors. Access the
underlying _elements list directly for wire, label, and symbol deletion.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove unused imports in move_schematic_component
- Add warning log when mirror attribute missing on rotate
- Move `import re` out of loop in annotate_schematic
- Include global_label in list_schematic_nets
- Fix SVG export to use directory for kicad-cli --output flag
- Return actual SVG data in get_schematic_view TS handler
- Add isError: true to all failure responses in new tools
- Add connect_passthrough to schematic category in registry
- Simplify power symbol filtering control flow in list_labels
- Fix indentation in list_schematic_labels power section
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Exports schematic to SVG via kicad-cli, then converts to PNG using
cairosvg (same approach as get_board_2d_view). Falls back gracefully
to SVG if cairosvg is not installed. Supports configurable output
size and format (png/svg).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add all missing schematic operations identified in the gap analysis:
Inspection (P0/P2):
- list_schematic_components: enumerate all components with refs, values, pins
- list_schematic_nets: list all nets with their connections
- list_schematic_wires: list all wire geometry
- list_schematic_labels: list net labels, global labels, power flags
Editing (P0/P1):
- annotate_schematic: assign ref designators to unannotated components (R? → R1)
- move_schematic_component: reposition placed symbols
- rotate_schematic_component: rotate/mirror placed symbols
- delete_schematic_wire: remove wires by coordinates
- delete_schematic_net_label: remove labels by name/position
Export (P1):
- export_schematic_svg: schematic SVG export via kicad-cli
- export_schematic_pdf: enhanced with file path return & blackAndWhite support
Also registers list_schematic_components and annotate_schematic as
direct tools (always visible) since they're prerequisites for
reference-based workflows.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
save_project uses project_commands.board, which was NOT in the propagation
list after pcbnew.LoadBoard() in _handle_import_svg_logo. As a result,
save_project() wrote the old in-memory board (without logo gr_poly entries)
back to disk, erasing the logo every time.
Fix: call _update_command_handlers() after reload, which updates all
command handler board references including project_commands.
Claude sends {shape:'rectangle', params:{x,y,width,height,...}} but handlers
were reading params.get('width') on the outer dict → None → wrong board size.
outline.py: extract inner = params.get('params', params) at method start,
read all dimensions from inner dict.
kicad_interface.py (_ipc_add_board_outline): delegate 'rectangle' to SWIG
path (same as 'rounded_rectangle') so Claude's shape+dims call is handled
correctly. For polygon IPC path: also unwrap inner params for points/width.