From 6633cd59fd8051a1f7c9d5dca3dfa2485f545b00 Mon Sep 17 00:00:00 2001 From: Eugene Mikhantyev Date: Sat, 14 Mar 2026 13:49:20 +0000 Subject: [PATCH] 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 --- python/kicad_interface.py | 124 ++++++++++++++++++++++++++++++++++++++ src/tools/registry.ts | 1 + src/tools/schematic.ts | 39 ++++++++++++ 3 files changed, 164 insertions(+) diff --git a/python/kicad_interface.py b/python/kicad_interface.py index 3a9ef26..bda174a 100644 --- a/python/kicad_interface.py +++ b/python/kicad_interface.py @@ -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") diff --git a/src/tools/registry.ts b/src/tools/registry.ts index bca2cf1..d2e0e45 100644 --- a/src/tools/registry.ts +++ b/src/tools/registry.ts @@ -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", diff --git a/src/tools/schematic.ts b/src/tools/schematic.ts index ef8fb67..594be86 100644 --- a/src/tools/schematic.ts +++ b/src/tools/schematic.ts @@ -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",