feat: add route_pad_to_pad tool

Convenience wrapper around route_trace that eliminates the need for
separate get_pad_position calls before routing.

- Accepts fromRef/fromPad/toRef/toPad instead of raw coordinates
- Automatically looks up pad positions from board footprints
- Auto-detects net from pad assignment (overridable via net param)
- Returns fromPad/toPad position info in response
- Saves ~2 tool calls (64+ calls for a full TMC2209 board) vs 3-step flow

Registered in: routing.py, kicad_interface.py (dispatch), routing.ts (MCP)
This commit is contained in:
Tom
2026-03-01 14:52:45 +01:00
parent b33d6e22fd
commit f0d738fff1
3 changed files with 119 additions and 0 deletions

View File

@@ -71,6 +71,103 @@ class RoutingCommands:
"errorDetails": str(e), "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]: def route_trace(self, params: Dict[str, Any]) -> Dict[str, Any]:
"""Route a trace between two points or pads""" """Route a trace between two points or pads"""
try: try:

View File

@@ -308,6 +308,7 @@ class KiCADInterface:
"add_text": self.board_commands.add_text, "add_text": self.board_commands.add_text,
"add_board_text": self.board_commands.add_text, # Alias for TypeScript tool "add_board_text": self.board_commands.add_text, # Alias for TypeScript tool
# Component commands # Component commands
"route_pad_to_pad": self.routing_commands.route_pad_to_pad,
"place_component": self._handle_place_component, "place_component": self._handle_place_component,
"move_component": self.component_commands.move_component, "move_component": self.component_commands.move_component,
"rotate_component": self.component_commands.rotate_component, "rotate_component": self.component_commands.rotate_component,

View File

@@ -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 // Copy routing pattern tool
server.tool( server.tool(
"copy_routing_pattern", "copy_routing_pattern",