kicad-mcp/src/mckicad/tools/power_symbols.py
Ryan Malloy 57872e59c1 Replace kicad-sch-api with internal SchDocument engine
Eliminate the external kicad-sch-api dependency by migrating all MCP
tools to the internal SchDocument class built on sexp_tree.py. This
fixes three serialization bugs (dropped global labels, TypeError on
local labels, mis-quoted property private keywords) and removes ~1,900
lines of workaround code.

New modules:
- sexp_tree.py: S-expression parser and round-trip serializer
- sch_document.py: schematic read/write/mutate API
- lib_resolver.py: symbol library search and resolution
- sch_helpers.py: shared load/validate/expand helpers

Migrated all 9 tool files, resources, and tests. Removed 5 workaround
functions from sexp_parser.py. 531 tests pass, ruff + mypy clean.
2026-07-11 16:47:22 -06:00

132 lines
4.5 KiB
Python

"""
Power symbol placement tool for the mckicad MCP server.
Attaches power rail symbols (GND, VCC, +3V3, etc.) to component pins
with automatic reference numbering, direction detection, and wire stubs.
Uses the shared geometry helpers from the patterns library.
"""
import logging
from typing import Any
from mckicad.server import mcp
from mckicad.utils.sch_helpers import expand as _expand
from mckicad.utils.sch_helpers import load_schematic, validate_schematic_path as _validate_schematic_path
logger = logging.getLogger(__name__)
def _get_schematic_engine() -> str:
return "sch_document"
@mcp.tool()
def add_power_symbol(
schematic_path: str,
net: str,
pin_ref: str,
pin_number: str,
lib_id: str | None = None,
stub_length: float = 5.08,
) -> dict[str, Any]:
"""Attach a power symbol (GND, VCC, +3V3, etc.) to a component pin.
Automatically determines the correct power library symbol from the net
name, assigns a ``#PWR`` reference, places the symbol above (supply)
or below (ground) the pin, and draws a connecting wire stub.
KiCad's connectivity engine treats a power symbol at a pin coordinate
as a direct electrical connection — no wire is required. This tool
places the symbol *offset* from the pin for visual clarity and draws
an explicit wire stub bridging the gap.
This is the recommended way to connect power rails to component pins --
it handles direction, grid alignment, and reference numbering for you.
For bulk power connections, see ``apply_batch`` (power_symbols section)
or the pattern tools (``place_decoupling_bank_pattern``, etc.).
Args:
schematic_path: Path to an existing .kicad_sch file.
net: Power net name, e.g. ``GND``, ``+3V3``, ``VCC``, ``+5V``.
The corresponding ``power:`` library symbol is auto-detected.
pin_ref: Reference designator of the target component (e.g. ``C1``, ``U3``).
pin_number: Pin number on the target component (e.g. ``1``, ``2``).
lib_id: Override the auto-detected power symbol library ID.
Only needed for non-standard symbols not in KiCad's power library.
stub_length: Wire stub length in mm between pin and symbol.
Defaults to 5.08 (2 grid units).
Returns:
Dictionary with ``success``, placed ``reference``, ``lib_id``,
symbol position, wire ID, and direction.
"""
verr = _validate_schematic_path(schematic_path)
if verr:
return verr
schematic_path = _expand(schematic_path)
if not net:
return {"success": False, "error": "net must be a non-empty string"}
if not pin_ref:
return {"success": False, "error": "pin_ref must be a non-empty string"}
if not pin_number:
return {"success": False, "error": "pin_number must be a non-empty string"}
try:
from mckicad.patterns._geometry import add_power_symbol_to_pin
from mckicad.utils.sexp_parser import resolve_pin_position
sch = load_schematic(schematic_path)
# Look up the target pin position (with sexp fallback for custom symbols)
pin_pos_tuple = resolve_pin_position(sch, schematic_path, pin_ref, pin_number)
if pin_pos_tuple is None:
return {
"success": False,
"error": (
f"Could not find pin {pin_number} on component {pin_ref}. "
f"Use get_component_pins to list available pins."
),
"schematic_path": schematic_path,
}
result = add_power_symbol_to_pin(
sch=sch,
pin_position=pin_pos_tuple,
net=net,
lib_id=lib_id,
stub_length=stub_length,
)
sch.save(schematic_path)
logger.info(
"Added %s power symbol %s to %s pin %s in %s",
net,
result["reference"],
pin_ref,
pin_number,
schematic_path,
)
return {
"success": True,
**result,
"target_component": pin_ref,
"target_pin": pin_number,
"schematic_path": schematic_path,
"engine": _get_schematic_engine(),
}
except Exception as e:
logger.error(
"Failed to add power symbol %s to %s.%s in %s: %s",
net, pin_ref, pin_number, schematic_path, e,
)
return {
"success": False,
"error": str(e),
"schematic_path": schematic_path,
}