Library symbols use y-up coordinates while schematics use y-down. The
_transform_local_point function was not negating y, causing asymmetric
symbols (e.g. power:VEE) to have their bounding boxes computed in the
wrong direction — missing overlaps with adjacent components.
Co-Authored-By: Claude Opus 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>
Parse graphical elements (rectangle, polyline, circle, arc, bezier) from
lib_symbols definitions to compute accurate symbol bounding boxes instead
of relying on pin positions with hardcoded degenerate expansion. This
fixes bbox accuracy for ICs (previously too small), tiny 2-pin passives
(previously too large), and single-pin symbols.
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>
The overlap detection was comparing center-to-center Euclidean distance with
a 0.5mm tolerance, missing components whose bodies physically overlap but have
different centers (e.g. a resistor placed inside an opamp triangle). Now uses
AABB intersection on pin-derived bounding boxes, matching the approach already
used by check_wire_collisions. Extracted shared bbox logic into
_compute_symbol_bbox_direct and _aabb_overlap helpers.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Previously, the endpoint suppression logic skipped any wire where at least
one endpoint touched a pin, hiding the case where both endpoints are pins
of the same component (a direct pin-to-pin short through the body).
Replace the single endpoint_matches_pin loop with separate start_at_pin /
end_at_pin checks; suppress only when exactly one endpoint is at a pin.
Add regression test to cover this case.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The same reference-lookup bug fixed in check_wire_collisions (where
PinLocator.get_all_symbol_pins always resolves to the first symbol
with a given reference) also affected:
- find_unconnected_pins: would check wrong pin positions for all
unannotated components after the first, producing false connected/
unconnected reports.
- get_elements_in_region: would return wrong pin coordinates for
unannotated components in the queried region.
Both now use _compute_pin_positions_direct (fetching pin defs by
lib_id and applying each symbol's own position/rotation/mirror),
matching the fix already applied to check_wire_collisions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PinLocator.get_all_symbol_pins resolves symbols by reference designator,
so when multiple components share the same unannotated reference (e.g. "Q?"),
it always returned the first match's pin positions. Every duplicate then
got an identical bounding box, causing a single wire to be flagged against
all N instances instead of only the ones it actually crosses.
Fix: add _compute_pin_positions_direct() that computes absolute pin positions
directly from each symbol's own (at x y rotation) and (mirror ...) data plus
pin definitions fetched by lib_id — no reference-name lookup involved.
Also extend _parse_symbols to capture mirror_x/mirror_y flags.
Add regression test: two "R?" at different positions, wire crossing only
one → must produce 0 collisions against the far-away component.
Co-Authored-By: Claude Sonnet 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>
Auto-detects runtime: Java 21+ direct or Docker/Podman fallback.
Discovers docker/podman via PATH instead of hardcoding.
README includes one-time setup with JAR download + Docker pull.
31 tests covering both execution modes.
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>
- Detects F.Cu<->B.Cu layer mismatch in route_pad_to_pad
- Splits route into: trace on start layer + via at midpoint + trace on end layer
- route_trace description warns: use route_pad_to_pad for cross-layer routing
- route_pad_to_pad description highlights automatic via insertion
Bug 1 — Rounded corners missing (src/tools/board.ts schema)
Symptom: Claude sends shape='rectangle' + radius=2.5, but the Zod schema
only listed 'rectangle', 'circle', 'polygon' as valid shapes. The JS
handler never passed cornerRadius through the IPC path, and Python only
generated arcs for shape='rounded_rectangle'. Result: 4 straight lines,
no arcs.
Fix: Added 'rounded_rectangle' and 'cornerRadius' to the Zod schema in
src/tools/board.ts so Claude can send the correct shape directly.
Also added a Python-side auto-upgrade: if shape='rectangle' and
cornerRadius>0, silently promote to 'rounded_rectangle' so legacy
callers still work (python/commands/board/outline.py).
Bug 2 — Board outline placed at (-w/2, -h/2) instead of (0, 0)
Symptom: A 30x30 mm board outline was placed centred at the origin
(start -15 -15) ... (end 15 15) instead of the expected top-left at
(0,0) → (30,30).
Root cause: src/tools/board.ts extracted x/y from params and renamed
them to centerX/centerY before forwarding to Python:
const { x, y, ...otherParams } = params;
callKicadScript('add_board_outline', { centerX: x, centerY: y, ...otherParams })
Python received centerX=0, centerY=0 and used that as the board centre,
placing the outline at (-15,-15)→(+15,+15).
Fix: Pass x/y directly as top-left corner coordinates. Python already
contains the correct logic: center = x + width/2, y + height/2. Removed
the incorrect rename in board.ts:
callKicadScript('add_board_outline', { shape, ...params })
Bug 3 — print() on stdout corrupted JSON-RPC protocol → Timeout
Symptom: Claude Desktop reported 'Timeout' for add_board_outline even
though Python completed the command in <5 ms. KiCAD was falsely launched.
Root cause: Debug print() statements written to stdout (the same channel
used for JSON-RPC responses) injected non-JSON lines into the protocol.
Node.js could not parse the response → timeout → Claude Desktop invented
a 'KiCAD UI required' workaround.
Fix: Removed all print() calls from outline.py. Logger.info/debug writes
to the log file (~/.kicad-mcp/logs/kicad_interface.log) and is safe.
Files changed:
src/tools/board.ts — Zod schema + removed centerX/centerY rename
python/commands/board/outline.py — auto-upgrade rectangle+radius, remove print()
- searchTools() previously only searched routed tool categories, so
direct tools like snapshot_project, save_project, create_project
returned 0 results when Claude called search_tools('snapshot')
- Direct tools are now searched first, with a hint that they must be
called directly (not via execute_tool)
Also includes routing.py zone-inset fix for rounded board corners
(already staged from previous session)