feat: add get_wire_connections tool for pin/wire lookup by schematic point

Given a single (x,y) coordinate on the schematic, flood-fills through all
connected wire segments and returns every component pin reachable on that net,
plus the full list of wire segments with their start/end coordinates.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Eugene Mikhantyev
2026-03-14 13:49:20 +00:00
parent 1647c282f3
commit 6633cd59fd
3 changed files with 164 additions and 0 deletions

View File

@@ -382,6 +382,7 @@ class KiCADInterface:
"connect_passthrough": self._handle_connect_passthrough,
"get_schematic_pin_locations": self._handle_get_schematic_pin_locations,
"get_net_connections": self._handle_get_net_connections,
"get_wire_connections": self._handle_get_wire_connections,
"run_erc": self._handle_run_erc,
"generate_netlist": self._handle_generate_netlist,
"sync_schematic_to_board": self._handle_sync_schematic_to_board,
@@ -2320,6 +2321,129 @@ class KiCADInterface:
logger.error(f"Error getting net connections: {str(e)}")
return {"success": False, "message": str(e)}
def _handle_get_wire_connections(self, params):
"""Find all component pins reachable from a point via connected wires"""
logger.info("Getting wire connections")
try:
from pathlib import Path
from commands.pin_locator import PinLocator
schematic_path = params.get("schematicPath")
x = params.get("x")
y = params.get("y")
if not all([schematic_path, x is not None, y is not None]):
return {"success": False, "message": "Missing required parameters: schematicPath, x, y"}
x, y = float(x), float(y)
tolerance = 0.5
def points_coincide(p1, p2):
return abs(p1[0] - p2[0]) < tolerance and abs(p1[1] - p2[1]) < tolerance
schematic = SchematicManager.load_schematic(schematic_path)
if not schematic:
return {"success": False, "message": "Failed to load schematic"}
if not hasattr(schematic, "wire"):
return {"success": False, "message": "Schematic has no wires"}
# Collect all wires as list of point sequences
all_wires = []
for wire in schematic.wire:
if hasattr(wire, "pts") and hasattr(wire.pts, "xy"):
pts = []
for point in wire.pts.xy:
if hasattr(point, "value"):
pts.append([float(point.value[0]), float(point.value[1])])
if len(pts) >= 2:
all_wires.append(pts)
# Step 1: Find all seed wires that touch the given point (start or end)
query_point = [x, y]
seed_wires = [
pts for pts in all_wires
if points_coincide(pts[0], query_point) or points_coincide(pts[-1], query_point)
]
if not seed_wires:
return {
"success": False,
"message": f"No wire found at ({x},{y}) within {tolerance}mm tolerance",
}
# Step 2: Flood-fill through connected wires
connected_wires = list(seed_wires)
frontier = set((pt[0], pt[1]) for pts in seed_wires for pt in pts)
remaining = [w for w in all_wires if w not in seed_wires]
changed = True
while changed:
changed = False
still_remaining = []
for pts in remaining:
wire_endpoints = {(pts[0][0], pts[0][1]), (pts[-1][0], pts[-1][1])}
if any(
points_coincide(list(ep), list(fp))
for ep in wire_endpoints
for fp in frontier
):
connected_wires.append(pts)
for pt in pts:
frontier.add((pt[0], pt[1]))
changed = True
else:
still_remaining.append(pts)
remaining = still_remaining
# Step 3: Collect all points from connected wires
connected_points = set()
for pts in connected_wires:
for pt in pts:
connected_points.add((pt[0], pt[1]))
# Step 4: Find component pins at connected points
if not hasattr(schematic, "symbol"):
return {"success": True, "pins": []}
locator = PinLocator()
pins = []
seen = set()
for symbol in schematic.symbol:
if not hasattr(symbol.property, "Reference"):
continue
ref = symbol.property.Reference.value
if ref.startswith("_TEMPLATE"):
continue
try:
all_pins = locator.get_all_symbol_pins(Path(schematic_path), ref)
if not all_pins:
continue
for pin_num, pin_data in all_pins.items():
pin_loc = [pin_data["x"], pin_data["y"]]
for cp in connected_points:
if points_coincide(pin_loc, list(cp)):
key = (ref, pin_num)
if key not in seen:
seen.add(key)
pins.append({"component": ref, "pin": pin_num})
break
except Exception as e:
logger.warning(f"Error checking pins for {ref}: {e}")
wires_out = [
{"start": {"x": pts[0][0], "y": pts[0][1]}, "end": {"x": pts[-1][0], "y": pts[-1][1]}}
for pts in connected_wires
]
return {"success": True, "pins": pins, "wires": wires_out}
except Exception as e:
logger.error(f"Error getting wire connections: {str(e)}")
import traceback
logger.error(traceback.format_exc())
return {"success": False, "message": str(e)}
def _handle_run_erc(self, params):
"""Run Electrical Rules Check on a schematic via kicad-cli"""
logger.info("Running ERC on schematic")

View File

@@ -102,6 +102,7 @@ export const toolCategories: ToolCategory[] = [
"list_schematic_nets",
"list_schematic_wires",
"list_schematic_labels",
"get_wire_connections",
"generate_netlist",
"sync_schematic_to_board",
"get_schematic_view",

View File

@@ -425,6 +425,45 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
},
);
// Get wire connections
server.tool(
"get_wire_connections",
"Find all component pins reachable from a schematic point via connected wires. Provide any point on a wire (start, end, or junction) to get all pins on that net.",
{
schematicPath: z.string().describe("Path to the schematic file"),
x: z.number().describe("X coordinate of the point on the wire"),
y: z.number().describe("Y coordinate of the point on the wire"),
},
async (args: { schematicPath: string; x: number; y: number }) => {
const result = await callKicadScript("get_wire_connections", args);
if (result.success && result.pins) {
const pinList = result.pins
.map((p: any) => ` - ${p.component}/${p.pin}`)
.join("\n");
const wireList = (result.wires ?? [])
.map((w: any) => ` - (${w.start.x},${w.start.y}) → (${w.end.x},${w.end.y})`)
.join("\n");
return {
content: [
{
type: "text",
text: `Pins connected at (${args.x},${args.y}):\n${pinList || " (none found)"}\n\nWire segments:\n${wireList || " (none)"}`,
},
],
};
} else {
return {
content: [
{
type: "text",
text: `Failed to get wire connections: ${result.message || "Unknown error"}`,
},
],
};
}
},
);
// Get pin locations for a schematic component
server.tool(
"get_schematic_pin_locations",