Commit Graph

192 Commits

Author SHA1 Message Date
Eugene Mikhantyev
0d5f5ea8a7 Merge pull request #124 from vdawger/fix/schematic-symbol-lookup-libname-ordering
fix: locate placed symbols when (lib_name) precedes (lib_id)
2026-04-25 23:10:51 +01:00
Eugene Mikhantyev
74990eabdf Merge pull request #119 from thesamprice/fix/add-component-subsheet-project-path
fix: resolve project-local sym-lib-table for schematics in sub-folders
2026-04-25 23:06:31 +01:00
Eugene Mikhantyev
86edc58168 Merge pull request #117 from thesamprice/fix/add-label-subsheet-fallback
fix: add_label falls back gracefully on sub-sheet schematics
2026-04-25 23:00:52 +01:00
William Viana
e96637c6c3 fix: locate placed symbols when (lib_name) precedes (lib_id)
KiCad serialises rescued or locally-customised library entries with an
extra (lib_name "...") child before (lib_id "..."):

    (symbol
      (lib_name "RESISTOR_0603_4")
      (lib_id "MF_Passives:RESISTOR_0603")
      (at 132.08 44.45 90)
      ...)

The block-matching regex in _handle_get_schematic_component,
_handle_edit_schematic_component, and _handle_delete_schematic_component
required (lib_id IMMEDIATELY after (symbol, so any placed component
using this form was silently invisible to lookup. The user-visible
symptom is "Component '<ref>' not found in schematic" even though the
component is plainly present (and reachable through list / IPC paths).
This bug also affected set/remove_schematic_component_property and the
existing footprint/value/reference rewriting paths in edit, since they
all share the same lookup code.

The parent-position lookup used a similarly-strict regex
((symbol (lib_id "...") (at ...))), which silently fell back to (0,0)
on (lib_name)-first symbols and caused new properties added through
the custom-properties path to anchor at the schematic origin instead
of the parent symbol.

Fix: relax the symbol-block opening pattern to (symbol\s+\( — matching
any opening paren after (symbol — and read the symbol's origin from
the first (at ...) inside the block. Library-definition entries inside
(lib_symbols ...) are still excluded by the existing range check
(they use the (symbol "name" ...) form with a quoted string, not a
paren).

Adds 7 regression tests in TestLibNameBeforeLibIdOrdering using a
real-world (lib_name)-first resistor block, covering get / edit /
set-property / remove-property / delete and verifying that newly
added properties anchor to the symbol origin instead of (0, 0).
2026-04-21 10:12:52 -07:00
mixelpixx
28d9f3353e Merge pull request #115 from thesamprice/test/add-schematic-component-unit
test: add unit parameter tests for add_schematic_component
2026-04-21 09:15:59 -04:00
mixelpixx
738f652eab Merge pull request #118 from thesamprice/fix/delete-label-all-types
fix: delete_label matches global_label and hierarchical_label types
2026-04-21 09:01:27 -04:00
mixelpixx
1b2b98fd45 Merge pull request #116 from thesamprice/feat/ipc-annotation-generator
feat: Claude-powered annotation generator for KiCad IPC API tools
2026-04-21 09:00:49 -04:00
mixelpixx
4d8dcf7dbb Merge pull request #103 from tecnovel/main
fix: correct pin location, symbol reference dedup, and ERC violation parsing
2026-04-21 09:00:09 -04:00
mixelpixx
74b4e717f8 Merge pull request #120 from thesamprice/feat/hierarchical-sync
feat: walk all sub-sheets to build hierarchical pad→net map for sync_schematic_to_board
2026-04-21 08:59:39 -04:00
mixelpixx
34feb5dcce Merge pull request #122 from vdawger/feat/multi-sheet-net-connectivity
feat: multi-sheet net connectivity + sexp-based parsing reliability
2026-04-21 08:58:51 -04:00
William Viana
4d7843c03a feat: support arbitrary custom properties on schematic components
Promotes BOM / sourcing fields (MPN, Manufacturer, DigiKey_PN, LCSC,
JLCPCB_PN, Voltage, Tolerance, Dielectric, ...) to first-class citizens
on placed schematic symbols.

New MCP tools:
- set_schematic_component_property: add or update one custom property
  on a component (convenience wrapper around edit_schematic_component).
- remove_schematic_component_property: delete one custom property.
  The four built-in fields (Reference, Value, Footprint, Datasheet) are
  protected and rejected.

edit_schematic_component enhancements:
- New `properties` parameter: map of property name to either a string
  value or a full spec object { value, x?, y?, angle?, hide?, fontSize? }.
  Adds the property when missing, otherwise updates the existing field
  (and optionally its label position / visibility). Lets a single tool
  call attach an entire BOM payload to a component.
- New `removeProperties` parameter: list of custom property names to
  delete in the same call.
- Property values are now backslash-escaped so descriptions containing
  a double-quote or a backslash no longer corrupt the .kicad_sch file.
- New properties default to (hide yes) so they appear in BOM exports
  without cluttering the schematic canvas.

get_schematic_component description clarified to highlight that it
already returns every field on the symbol, including custom ones.

New MCP prompt component_sourcing_properties guides agents through the
conventional property names recognised by downstream BOM tooling and
the recommended call sequence.

Implementation (python/kicad_interface.py):
- _PROTECTED_PROPERTY_FIELDS frozenset
- _escape_sexpr_string / _find_matching_paren static helpers
- _set_property_in_block / _set_hide_on_property /
  _remove_property_from_block surgical text-level edits that preserve
  formatting and the property's UUID
- _handle_edit_schematic_component rewritten to orchestrate
  add/update/remove and return a per-property summary
- New handlers _handle_set_schematic_component_property and
  _handle_remove_schematic_component_property registered in the
  command dispatch table

Tests (tests/test_schematic_component_properties.py):
32 tests covering escape helper, paren matcher, add/update/remove
(single + batched), full spec dicts, default position, default
(hide yes), special-character escaping, UUID preservation, protected
built-in field rejection, no-op removal, both new convenience tools,
and input validation. All 590 tests in the project still pass.

Docs: README, SCHEMATIC_TOOLS_REFERENCE, TOOL_INVENTORY, CHANGELOG.
2026-04-20 17:36:38 -07:00
William Viana
046f33d876 feat: multi-sheet net connectivity + sexp-based parsing reliability
Adds robust multi-sheet (hierarchical) net connectivity for KiCad
schematics and switches the wire/label parsing to a direct sexpdata
pipeline that bypasses kicad-skip's collection iteration, which was
silently dropping wires, labels, and symbol instances on real-world
schematics.

python/commands/wire_connectivity.py
  - New sexpdata helpers: _load_sexp, _parse_wires_sexp,
    _parse_labels_sexp, _parse_symbol_instances_sexp,
    _parse_hierarchical_labels_sexp, _discover_sub_sheets.
  - _build_adjacency now detects T-junctions (endpoint landing on
    another wire's interior segment) so adjacency captures connections
    KiCad doesn't represent as separate wire segments.
  - _find_connected_wires gains an interior-segment fallback so labels
    placed mid-wire still seed BFS correctly.
  - _parse_virtual_connections gathers label / global_label /
    hierarchical_label and power-symbol pin positions, with a
    kicad-skip fallback for unit tests that mock the schematic.
  - _find_pins_on_net rebuilds pin positions from sexpdata symbol
    instances (with mirror_x/mirror_y/rotation handling) and uses a
    plus/minus 1 IU tolerance for floating-point edge cases.
  - get_connections_for_net walks the top sheet plus every recursively
    discovered sub-sheet, deduping pins across sheets.

python/commands/pin_locator.py
  - lib_id matching now falls back to a bare-name + unit-suffix match
    so instances like "stat-tis-custom:BAT_18650" resolve to
    lib_symbols entries like "BAT_18650_3".
  - Pin position math now y-negates lib_symbols coords, applies
    mirror_x/mirror_y in local coords before rotation, and propagates
    the same transform into get_pin_orientation so downstream callers
    get a correct outward angle for mirrored symbols.

python/commands/connection_schematic.py
  - generate_netlist now collects nets from both label and
    global_label and routes them through get_connections_for_net so
    netlists reflect cross-sheet connectivity instead of single-sheet
    label-only nets.

python/kicad_interface.py
  - list_schematic_nets aggregates net names across the top sheet and
    all sub-sheets via the sexp helpers, then resolves connections
    using get_connections_for_net.
  - get_net_connections delegates to get_connections_for_net for
    consistent multi-sheet results.
2026-04-20 15:59:22 -07:00
Noah Piqué
ef42eb60bb fix: correct pin location calculation and symbol reference dedup in kicad-skip
Three bugs fixed in the schematic component and pin locator pipeline:

1. component_schematic: remove redundant symbol.append() after clone()
   kicad-skip's clone() already inserts the raw element into the schematic
   tree. The subsequent NamedCollection.append() detects the reference as
   already registered (from the elementRename triggered by setting
   property.Reference.value) and renames it "R1_" with a trailing
   underscore, causing all subsequent pin lookups to fail.

2. pin_locator: negate lib y coordinate before rotation
   lib_symbols in .kicad_sch use library y-up convention; schematic
   coordinates use y-down. get_pin_location now negates pin_rel_y before
   applying rotation, matching KiCad's own transform order (same approach
   as _transform_local_point in schematic_analysis.py).

3. pin_locator: add .rstrip("_") guard in all symbol reference lookups
   Defensive guard against any residual cases where kicad-skip writes a
   trailing underscore to the Reference property value.

Also fixes the self-test script to use template_with_symbols.kicad_sch
(which contains placed _TEMPLATE_* symbols) rather than the expanded
template (which only contains lib_symbols definitions and has no cloneable
instances).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 13:10:47 +02:00
Eugene Mikhantyev
e5916005a0 feat: add add_schematic_text and list_schematic_texts tools
Adds two new MCP tools for working with free-form text annotations
(SCH_TEXT elements) in KiCad schematics:

- add_schematic_text: place a text note with optional angle, font size,
  bold/italic, and justification
- list_schematic_texts: list all text annotations with optional
  case-insensitive substring filter

Includes WireManager.add_text / list_texts using _text_insert + sexpdata,
handler dispatch in KiCADInterface, TypeScript tool definitions, registry
entry, reference doc updates, and 30 unit tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 21:26:39 +01:00
Samuel Price
0cff3c749c feat: walk all sub-sheets to build hierarchical pad→net map for sync
Hierarchical KiCad schematics store all components in sub-sheets;
the top-level .kicad_sch only contains sheet references. The previous
sync_schematic_to_board implementation called generate_netlist on the
top-level file only, which has no components, so it always returned
0 pads assigned.

Replace with _build_hierarchical_pad_net_map which:
- rglobs all .kicad_sch files in the project directory
- For each sheet, collects label positions from label, global_label,
  and hierarchical_label via skip.Schematic
- Adds power symbol (#PWR/#FLG) positions using their Value as net name
- Builds a wire adjacency graph and BFS-propagates net names through
  wire segments to reach pins not directly under a label
- Calls PinLocator.get_all_symbol_pins to get absolute pin positions,
  then matches to the propagated net map within 0.5 mm tolerance

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 12:48:01 -04:00
Samuel Price
91f4a05a00 feat: load IPC API annotations at startup to enrich tool descriptions
Adds python/annotations/loader.py (AnnotationLoader) which resolves MCP
tool names to KiCad IPC proto message annotations using three layers:
  1. Explicit TOOL_TO_PROTO mapping for non-obvious name pairs
  2. Automatic snake_case → PascalCase conversion
  3. Suffix-stripped variants (get_nets_list → GetNets)

Integrates into kicad_interface.py tools/list handler:
  - Tools with existing schemas: enrich_schema() adds blocking/interactive
    ToolAnnotations hints and fills description gaps
  - Tools without schemas (fallback): gets a real description from the
    IPC annotation instead of the generic "KiCAD command: <name>" string

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 12:15:43 -04:00
Samuel Price
8253550715 data: add generated KiCad IPC API tool annotations (master, 73 messages)
Generated by scripts/generate_tool_annotations.py --fetch-from-gitlab --use-cli.
Covers all command messages across board, schematic, and common proto files.
Stored in python/annotations/ (data/ is gitignored for large JLCPCB databases).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 12:15:43 -04:00
Samuel Price
6578667197 fix: resolve project-local sym-lib-table for schematics in sub-folders
When add_schematic_component is called with a schematic that lives in a
sub-folder (e.g. sheets/rs485.kicad_sch), the handler derived the project
path as schematic.parent — which in a hierarchical project is the sheets/
directory, not the project root.  Any project-local symbol library declared
in sym-lib-table (using ${KIPRJMOD}) is invisible to DynamicSymbolLoader
from that directory, causing "Symbol 'X' not found in library 'Y'" even
though the library file and sym-lib-table entry both exist.

Fix: walk the ancestor chain from the schematic file upward until a
directory is found that contains a sym-lib-table file or a .kicad_pro
file, then use that as the project root for library resolution.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 12:02:54 -04:00
Samuel Price
1767a269b0 fix: delete_label matches global_label and hierarchical_label types
WireManager.delete_label only checked for Symbol("label") when scanning
the schematic s-expression list, so it silently skipped and failed to
delete global_label and hierarchical_label elements — returning False
with "No matching label found" even when a visible label existed at the
given coordinates.

Fix: collect all three label-like symbol types into a set and use `in`
instead of `==` for the type check.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 12:02:30 -04:00
Samuel Price
6e56ccdd59 fix: add_label falls back gracefully on sub-sheet schematics
In hierarchical KiCad designs, sub-sheets (.kicad_sch files referenced
via (sheet ...) blocks) do not have a (sheet_instances) section — that
only appears in the top-level schematic.

WireManager.add_label was returning False with a logged error whenever
no (sheet_instances) marker was found, making it impossible to add any
net label (local, global, or hierarchical) to a sub-sheet.

Fix: fall back to inserting before the final item of the s-expression
list, which is equivalent to appending before the closing ')' of the
outer (kicad_sch ...) block.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 12:02:03 -04:00
Samuel Price
78e3b8860a feat: add unit parameter to add_schematic_component for multi-unit symbols
Allows placing a specific unit (A=1, B=2, C=3, …) of a multi-unit KiCad
symbol rather than always defaulting to unit 1. Required for quad
optocouplers, dual op-amps, and other multi-unit parts where each channel
must be placed independently.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 10:11:15 -04:00
Noah Piqué
ab2500fb8d fix: correct pin location calculation and symbol reference dedup in kicad-skip
Three bugs fixed in the schematic component and pin locator pipeline:

1. component_schematic: remove redundant symbol.append() after clone()
   kicad-skip's clone() already inserts the raw element into the schematic
   tree. The subsequent NamedCollection.append() detects the reference as
   already registered (from the elementRename triggered by setting
   property.Reference.value) and renames it "R1_" with a trailing
   underscore, causing all subsequent pin lookups to fail.

2. pin_locator: negate lib y coordinate before rotation
   lib_symbols in .kicad_sch use library y-up convention; schematic
   coordinates use y-down. get_pin_location now negates pin_rel_y before
   applying rotation, matching KiCad's own transform order (same approach
   as _transform_local_point in schematic_analysis.py).

3. pin_locator: add .rstrip("_") guard in all symbol reference lookups
   Defensive guard against any residual cases where kicad-skip writes a
   trailing underscore to the Reference property value.

Also fixes the self-test script to use template_with_symbols.kicad_sch
(which contains placed _TEMPLATE_* symbols) rather than the expanded
template (which only contains lib_symbols definitions and has no cloneable
instances).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 23:31:14 +01:00
Eugene Mikhantyev
c8f6a58116 feat: add move_schematic_net_label tool
Moves a net label (local, global, or hierarchical) to a new position in
place, avoiding the error-prone delete-then-re-add workflow. Supports an
optional currentPosition disambiguator and labelType filter.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-18 23:14:29 +01:00
Eugene Mikhantyev
e06082bb44 chore: normalize schematic.py and apply black to create_schematic signature
Bundles two changes for python/commands/schematic.py:
  1. CRLF → LF line-ending normalization (matches the bulk renormalize
     from the previous commit — was held back here because black would
     also need to re-reformat it).
  2. Black reflow of create_schematic()'s parameter list, which exceeded
     the line length. Pre-existing drift, no logic change.

`git show --ignore-all-space HEAD` shows just the 2-line signature diff.
2026-04-18 15:24:02 +01:00
Eugene Mikhantyev
bfc25639c2 chore: normalize all tracked files to LF line endings
Mechanical application of the `.gitattributes` rules from the prior commit.
All 50 files differ only in line endings — verified by
`git diff --cached --ignore-all-space` being empty.

Before: main had 42 CRLF + 27 LF Python files plus mixed-ending in YAML,
templates, and shell scripts. After: every text file is LF (except the
Windows-native *.ps1, *.bat scripts which remain CRLF per gitattributes).

This eliminates the noisy-diff failure mode seen in PR #102, where a
small logic change produced a 918-line diff due to whole-file CRLF→LF
conversion.
2026-04-18 15:23:00 +01:00
Eugene Mikhantyev
0bc31b2043 refactor: remove dead list_library_symbols stub
The placeholder static method on LibraryManager (library_schematic.py)
returned [] and had no dispatch entry or caller. The real tool of the
same name lives in library_symbol.py.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-18 15:14:53 +01:00
Eugene Mikhantyev
9a66f5e0b9 feat: add netName and labelType filters to list_schematic_labels
Add optional netName (exact case-sensitive match) and labelType
(net/global/power enum) parameters. Both are optional and AND
together when combined. Omitting both preserves current behaviour.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-18 15:07:23 +01:00
Eugene Mikhantyev
4ec63f7544 Merge pull request #107 from mixelpixx/fix/get-symbol-info-spice-opamp
fix: get_symbol_info returns neighboring symbol's data for short blocks
2026-04-18 14:46:49 +01:00
Eugene Mikhantyev
e5cd924b0a Merge pull request #102 from ffindog/fix/netlist-label-at-pin
fix: resolve nets when labels are placed directly at pin endpoints
2026-04-18 14:37:46 +01:00
Tom
a422e4eb0c Merge pull request #87 from tnemrap/fix/create-schematic-path
fix: create_schematic now respects the path parameter
2026-04-18 14:33:58 +02:00
Tom
56e6aead88 Update python/commands/schematic.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-18 14:31:22 +02:00
Tom
75a0b764d5 Update python/kicad_interface.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-18 14:31:09 +02:00
Eugene Mikhantyev
55ec4950d9 fix: get_symbol_info returns wrong data for every small symbol
Parser used a 5000-char heuristic slice per symbol; any symbol block
shorter than 5000 chars bled into the next one, and last-write-wins in
the properties dict ensured the neighbor's data clobbered the target.
Reported as Simulation_SPICE:OPAMP returning PJFET data.

Switch to parenthesis-depth tracking to find the true end of each
(symbol ...) block. Also surface Sim.Pins so agents can read opamp pin
numbering without inferring it from schematic placement.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-18 12:55:41 +01:00
Eugene Mikhantyev
e687fa39e1 Merge pull request #104 from LeahArmstrong/feat/hierarchy-tools
feat: add hierarchical label and sheet pin tools
2026-04-18 12:00:22 +01:00
Leah Armstrong
101b4e1dad feat: add hierarchy tools (hierarchical label + sheet pin)
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.
2026-04-15 12:32:09 -04:00
ffindog
ef660afdb4 fix: resolve nets when labels are placed directly at pin endpoints
get_net_connections() built its match-point set exclusively from wire
endpoints. If a net label was placed directly at a pin endpoint with no
wire segment (valid KiCad style), the function returned 0 connections
because connected_wire_points was empty.

Fix: build all_match_points as the union of connected wire endpoints and
label positions. Pin matching checks both, so label-at-pin schematics
produce correct netlists alongside traditional wired schematics.

Also handles the case where the schematic object has no wire attribute
at all — instead of returning early, we continue with label positions
as the sole match points.

Tests: tests/test_label_at_pin_net_connections.py (11 unit tests)
  - label at pin, no wire → pin found
  - label at pin, within/outside tolerance
  - label via wire → still found (regression)
  - mixed wired and direct labels on same net
  - no wire attribute → still detects label-at-pin
  - template symbols skipped

Black/isort/flake8/mypy verified manually (pre-commit local npm hook
fails to install on Windows due to MobaXterm path environment issue).
2026-04-15 22:40:14 +10:00
ffindog
5d87d9bc74 merge: upstream/main (57 commits) — preserve PR #102 net label fix
Merged upstream/main into our fork. Conflict in connection_schematic.py
resolved by taking upstream's file and re-applying our fix:
- all_match_points = connected_wire_points | label positions
- Allows nets where labels are placed directly at pin endpoints (no wire)

Upstream changes include: security fixes (8 vulns), new schematic tools
(get_net_at_point, find_orphaned_wires, snap_to_grid, get_wire_connections),
generate_netlist rewrite via kicad-cli, wire preservation on component move,
schematic analysis tools, KiCad 10 support.
2026-04-15 22:27:41 +10:00
ffindog
00a59a590b fix: resolve nets when labels are placed directly at pin endpoints
get_net_connections() previously required wire segments between net
labels and component pins. If a label was placed exactly at a pin
endpoint (no wire), the net was silently dropped and generate_netlist
returned 0 nets.

Fix: build all_match_points as the union of connected wire endpoints
and label positions. Pin matching now checks both, so label-at-pin
schematics (valid KiCad style) produce correct netlists alongside
traditional wired schematics.
2026-04-15 22:10:22 +10:00
Leah Armstrong
15d06e449a fix: ERC handler fails on KiCad 9 schematics
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.
2026-04-14 12:52:17 -04:00
Eugene Mikhantyev
c7b0e3105b fix: implement export_netlist handler and rewrite generate_netlist to use kicad-cli
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>
2026-04-12 18:52:46 +01:00
Eugene Mikhantyev
ba09fc4e0f fix: change snap_to_grid default grid from 2.54mm to 1.27mm (50 mil)
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>
2026-04-12 18:10:55 +01:00
Eugene Mikhantyev
1df6332aa8 refactor: address code review issues — remove stale import, fix misleading var name, eliminate redundant adjacency build, add warning log on pin-angle failure
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 17:32:18 +01:00
Eugene Mikhantyev
4f733bb3db refactor: consolidate get_pin_net into get_wire_connections
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>
2026-04-12 17:02:35 +01:00
Eugene Mikhantyev
f73d8d9795 feat: warn on case-mismatched net label names in add_schematic_net_label
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 15:49:15 +01:00
Eugene Mikhantyev
4895bf169c feat: add connected_pin_count to list_schematic_nets and list_floating_labels tool
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 15:46:32 +01:00
Eugene Mikhantyev
e826cf3d32 feat: add get_net_at_point tool for coordinate-based net lookup
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 15:29:46 +01:00
Eugene Mikhantyev
5f3c20d308 feat: add get_pin_net tool for direct net/pin queries
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>
2026-04-12 15:19:14 +01:00
Eugene Mikhantyev
9387683368 feat: add snap_to_grid schematic tool
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>
2026-04-12 15:03:35 +01:00
Eugene Mikhantyev
58bb08a252 feat: add find_orphaned_wires schematic analysis tool
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>
2026-04-12 14:44:12 +01:00
Eugene Mikhantyev
94125eda7f fix: add pin-snapping and coordinate feedback to net label tools
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>
2026-04-12 14:12:11 +01:00