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.
132 lines
4.5 KiB
Python
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,
|
|
}
|