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.
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>
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>
The Python executable and PYTHONPATH were hardcoded to Windows paths,
causing the server to fail silently on macOS/Linux. Now reads
KICAD_PYTHON from the environment (set by setup-macos.sh) with
python3 as the fallback.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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.
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.
Introduces a three-layer defence against CRLF/LF drift:
- `.gitattributes` normalizes all text files to LF on commit and checkout;
Windows-native scripts (*.bat, *.cmd, *.ps1) explicitly keep CRLF.
- `.editorconfig` instructs editors (VS Code, JetBrains, vim, etc.) to save
with LF by default so the problem is prevented at the source.
- `mixed-line-ending --fix=lf` hook in pre-commit catches anything that
slips through and auto-fixes it before commit.
No file contents are normalized in this commit — that follows in a
separate `git add --renormalize .` commit for easier review.
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>
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>
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>
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.
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).
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.
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>