diff --git a/python/commands/routing.py b/python/commands/routing.py index 07e3d65..d64d19c 100644 --- a/python/commands/routing.py +++ b/python/commands/routing.py @@ -71,6 +71,103 @@ class RoutingCommands: "errorDetails": str(e), } + def route_pad_to_pad(self, params: Dict[str, Any]) -> Dict[str, Any]: + """Route a trace directly from one component pad to another. + + Looks up pad positions automatically, then creates a trace. + Convenience wrapper around route_trace that eliminates the need + for separate get_pad_position calls. + """ + try: + if not self.board: + return { + "success": False, + "message": "No board is loaded", + "errorDetails": "Load or create a board first", + } + + from_ref = params.get("fromRef") + from_pad = str(params.get("fromPad", "")) + to_ref = params.get("toRef") + to_pad = str(params.get("toPad", "")) + layer = params.get("layer", "F.Cu") + width = params.get("width") + net = params.get("net") # optional override + + if not from_ref or not from_pad or not to_ref or not to_pad: + return { + "success": False, + "message": "Missing parameters", + "errorDetails": "fromRef, fromPad, toRef, toPad are all required", + } + + scale = 1000000 # nm to mm + + # Find pads + footprints = {fp.GetReference(): fp for fp in self.board.GetFootprints()} + + for ref in [from_ref, to_ref]: + if ref not in footprints: + return { + "success": False, + "message": f"Component not found: {ref}", + "errorDetails": f"'{ref}' does not exist on the board", + } + + def find_pad(ref: str, pad_num: str): + fp = footprints[ref] + for pad in fp.Pads(): + if pad.GetNumber() == pad_num: + return pad + return None + + start_pad = find_pad(from_ref, from_pad) + end_pad = find_pad(to_ref, to_pad) + + if not start_pad: + return { + "success": False, + "message": f"Pad not found: {from_ref} pad {from_pad}", + "errorDetails": f"Check pad number for {from_ref}", + } + if not end_pad: + return { + "success": False, + "message": f"Pad not found: {to_ref} pad {to_pad}", + "errorDetails": f"Check pad number for {to_ref}", + } + + start_pos = start_pad.GetPosition() + end_pos = end_pad.GetPosition() + + # Use net from start pad if not overridden + if not net: + net = start_pad.GetNetname() or end_pad.GetNetname() or "" + + # Delegate to route_trace + result = self.route_trace({ + "start": {"x": start_pos.x / scale, "y": start_pos.y / scale, "unit": "mm"}, + "end": {"x": end_pos.x / scale, "y": end_pos.y / scale, "unit": "mm"}, + "layer": layer, + "width": width, + "net": net, + }) + + if result.get("success"): + result["message"] = f"Routed {from_ref}.{from_pad} → {to_ref}.{to_pad} (net: {net or 'none'})" + result["fromPad"] = {"ref": from_ref, "pad": from_pad, "x": start_pos.x / scale, "y": start_pos.y / scale} + result["toPad"] = {"ref": to_ref, "pad": to_pad, "x": end_pos.x / scale, "y": end_pos.y / scale} + + return result + + except Exception as e: + logger.error(f"Error in route_pad_to_pad: {str(e)}") + return { + "success": False, + "message": "Failed to route pad to pad", + "errorDetails": str(e), + } + def route_trace(self, params: Dict[str, Any]) -> Dict[str, Any]: """Route a trace between two points or pads""" try: diff --git a/python/kicad_interface.py b/python/kicad_interface.py index a396074..2fde2a7 100644 --- a/python/kicad_interface.py +++ b/python/kicad_interface.py @@ -308,6 +308,7 @@ class KiCADInterface: "add_text": self.board_commands.add_text, "add_board_text": self.board_commands.add_text, # Alias for TypeScript tool # Component commands + "route_pad_to_pad": self.routing_commands.route_pad_to_pad, "place_component": self._handle_place_component, "move_component": self.component_commands.move_component, "rotate_component": self.component_commands.rotate_component, diff --git a/src/tools/routing.ts b/src/tools/routing.ts index e1a2e05..691a903 100644 --- a/src/tools/routing.ts +++ b/src/tools/routing.ts @@ -322,6 +322,27 @@ export function registerRoutingTools( }, ); + // Route pad to pad tool + server.tool( + "route_pad_to_pad", + "Route a trace directly from one component pad to another without needing separate get_pad_position calls. Automatically looks up pad coordinates and uses the pad's net. Saves token usage compared to the 3-step get_pad_position + get_pad_position + route_trace sequence.", + { + fromRef: z.string().describe("Reference of the source component (e.g. 'U2')"), + fromPad: z.union([z.string(), z.number()]).describe("Pad number on the source component (e.g. '6' or 6)"), + toRef: z.string().describe("Reference of the target component (e.g. 'U1')"), + toPad: z.union([z.string(), z.number()]).describe("Pad number on the target component (e.g. '15' or 15)"), + layer: z.string().optional().describe("PCB layer (default: F.Cu)"), + width: z.number().optional().describe("Trace width in mm (default: board default)"), + net: z.string().optional().describe("Net name override (default: auto-detected from pad)"), + }, + async (args: any) => { + const result = await callKicadScript("route_pad_to_pad", args); + return { + content: [{ type: "text", text: JSON.stringify(result, null, 2) }], + }; + }, + ); + // Copy routing pattern tool server.tool( "copy_routing_pattern",