feat(mcp): add route_arc_trace for true PCB arc primitives (SWIG + IPC) (#165)
This commit is contained in:
@@ -351,6 +351,81 @@ class RoutingCommands:
|
|||||||
"errorDetails": str(e),
|
"errorDetails": str(e),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def route_arc_trace(self, params: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""Route a copper arc trace from start/mid/end points."""
|
||||||
|
try:
|
||||||
|
if not self.board:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"message": "No board is loaded",
|
||||||
|
"errorDetails": "Load or create a board first",
|
||||||
|
}
|
||||||
|
|
||||||
|
start = params.get("start")
|
||||||
|
mid = params.get("mid")
|
||||||
|
end = params.get("end")
|
||||||
|
layer = params.get("layer", "F.Cu")
|
||||||
|
width = params.get("width")
|
||||||
|
net = params.get("net")
|
||||||
|
|
||||||
|
if not start or not mid or not end:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"message": "Missing parameters",
|
||||||
|
"errorDetails": "start, mid and end points are required",
|
||||||
|
}
|
||||||
|
|
||||||
|
layer_id = self.board.GetLayerID(layer)
|
||||||
|
if layer_id < 0:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"message": "Invalid layer",
|
||||||
|
"errorDetails": f"Layer '{layer}' does not exist",
|
||||||
|
}
|
||||||
|
|
||||||
|
start_point = self._get_point(start)
|
||||||
|
mid_point = self._get_point(mid)
|
||||||
|
end_point = self._get_point(end)
|
||||||
|
|
||||||
|
arc = pcbnew.PCB_ARC(self.board)
|
||||||
|
arc.SetStart(start_point)
|
||||||
|
arc.SetMid(mid_point)
|
||||||
|
arc.SetEnd(end_point)
|
||||||
|
arc.SetLayer(layer_id)
|
||||||
|
|
||||||
|
if width:
|
||||||
|
arc.SetWidth(int(width * 1000000))
|
||||||
|
else:
|
||||||
|
arc.SetWidth(self.board.GetDesignSettings().GetCurrentTrackWidth())
|
||||||
|
|
||||||
|
if net:
|
||||||
|
netinfo = self.board.GetNetInfo()
|
||||||
|
nets_map = netinfo.NetsByName()
|
||||||
|
if nets_map.has_key(net):
|
||||||
|
arc.SetNet(nets_map[net])
|
||||||
|
|
||||||
|
self.board.Add(arc)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"message": "Added arc trace",
|
||||||
|
"arc": {
|
||||||
|
"start": {"x": start_point.x / 1000000, "y": start_point.y / 1000000, "unit": "mm"},
|
||||||
|
"mid": {"x": mid_point.x / 1000000, "y": mid_point.y / 1000000, "unit": "mm"},
|
||||||
|
"end": {"x": end_point.x / 1000000, "y": end_point.y / 1000000, "unit": "mm"},
|
||||||
|
"layer": layer,
|
||||||
|
"width": arc.GetWidth() / 1000000,
|
||||||
|
"net": net,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error routing arc trace: {str(e)}")
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"message": "Failed to route arc trace",
|
||||||
|
"errorDetails": str(e),
|
||||||
|
}
|
||||||
|
|
||||||
def add_via(self, params: Dict[str, Any]) -> Dict[str, Any]:
|
def add_via(self, params: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
"""Add a via at the specified location"""
|
"""Add a via at the specified location"""
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -861,6 +861,71 @@ class IPCBoardAPI(BoardAPI):
|
|||||||
logger.error(f"Failed to add track: {e}")
|
logger.error(f"Failed to add track: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def add_arc_track(
|
||||||
|
self,
|
||||||
|
start_x: float,
|
||||||
|
start_y: float,
|
||||||
|
mid_x: float,
|
||||||
|
mid_y: float,
|
||||||
|
end_x: float,
|
||||||
|
end_y: float,
|
||||||
|
width: float = 0.25,
|
||||||
|
layer: str = "F.Cu",
|
||||||
|
net_name: Optional[str] = None,
|
||||||
|
) -> bool:
|
||||||
|
"""Add a copper arc track to the board."""
|
||||||
|
try:
|
||||||
|
from kipy.board_types import ArcTrack
|
||||||
|
from kipy.geometry import Vector2
|
||||||
|
from kipy.proto.board.board_types_pb2 import BoardLayer
|
||||||
|
from kipy.util.units import from_mm
|
||||||
|
|
||||||
|
board = self._get_board()
|
||||||
|
|
||||||
|
arc = ArcTrack()
|
||||||
|
arc.start = Vector2.from_xy(from_mm(start_x), from_mm(start_y))
|
||||||
|
arc.mid = Vector2.from_xy(from_mm(mid_x), from_mm(mid_y))
|
||||||
|
arc.end = Vector2.from_xy(from_mm(end_x), from_mm(end_y))
|
||||||
|
arc.width = from_mm(width)
|
||||||
|
|
||||||
|
layer_map = {
|
||||||
|
"F.Cu": BoardLayer.BL_F_Cu,
|
||||||
|
"B.Cu": BoardLayer.BL_B_Cu,
|
||||||
|
"In1.Cu": BoardLayer.BL_In1_Cu,
|
||||||
|
"In2.Cu": BoardLayer.BL_In2_Cu,
|
||||||
|
}
|
||||||
|
arc.layer = layer_map.get(layer, BoardLayer.BL_F_Cu)
|
||||||
|
|
||||||
|
if net_name:
|
||||||
|
nets = board.get_nets()
|
||||||
|
for net in nets:
|
||||||
|
if net.name == net_name:
|
||||||
|
arc.net = net
|
||||||
|
break
|
||||||
|
|
||||||
|
commit = board.begin_commit()
|
||||||
|
board.create_items(arc)
|
||||||
|
board.push_commit(commit, "Added arc track")
|
||||||
|
|
||||||
|
self._notify(
|
||||||
|
"arc_track_added",
|
||||||
|
{
|
||||||
|
"start": {"x": start_x, "y": start_y},
|
||||||
|
"mid": {"x": mid_x, "y": mid_y},
|
||||||
|
"end": {"x": end_x, "y": end_y},
|
||||||
|
"width": width,
|
||||||
|
"layer": layer,
|
||||||
|
"net": net_name,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
logger.info(
|
||||||
|
f"Added arc track start=({start_x}, {start_y}) mid=({mid_x}, {mid_y}) end=({end_x}, {end_y}) mm"
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to add arc track: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
def add_via(
|
def add_via(
|
||||||
self,
|
self,
|
||||||
x: float,
|
x: float,
|
||||||
|
|||||||
@@ -364,6 +364,7 @@ class KiCADInterface:
|
|||||||
# Routing commands
|
# Routing commands
|
||||||
"add_net": self.routing_commands.add_net,
|
"add_net": self.routing_commands.add_net,
|
||||||
"route_trace": self.routing_commands.route_trace,
|
"route_trace": self.routing_commands.route_trace,
|
||||||
|
"route_arc_trace": self.routing_commands.route_arc_trace,
|
||||||
"add_via": self.routing_commands.add_via,
|
"add_via": self.routing_commands.add_via,
|
||||||
"delete_trace": self.routing_commands.delete_trace,
|
"delete_trace": self.routing_commands.delete_trace,
|
||||||
"query_traces": self.routing_commands.query_traces,
|
"query_traces": self.routing_commands.query_traces,
|
||||||
@@ -488,6 +489,7 @@ class KiCADInterface:
|
|||||||
IPC_CAPABLE_COMMANDS = {
|
IPC_CAPABLE_COMMANDS = {
|
||||||
# Routing commands
|
# Routing commands
|
||||||
"route_trace": "_ipc_route_trace",
|
"route_trace": "_ipc_route_trace",
|
||||||
|
"route_arc_trace": "_ipc_route_arc_trace",
|
||||||
"add_via": "_ipc_add_via",
|
"add_via": "_ipc_add_via",
|
||||||
"add_net": "_ipc_add_net",
|
"add_net": "_ipc_add_net",
|
||||||
"delete_trace": "_ipc_delete_trace",
|
"delete_trace": "_ipc_delete_trace",
|
||||||
@@ -606,6 +608,7 @@ class KiCADInterface:
|
|||||||
"rotate_component",
|
"rotate_component",
|
||||||
"delete_component",
|
"delete_component",
|
||||||
"route_trace",
|
"route_trace",
|
||||||
|
"route_arc_trace",
|
||||||
"route_pad_to_pad",
|
"route_pad_to_pad",
|
||||||
"add_via",
|
"add_via",
|
||||||
"delete_trace",
|
"delete_trace",
|
||||||
@@ -5006,6 +5009,61 @@ print("ok")
|
|||||||
logger.error(f"IPC route_trace error: {e}")
|
logger.error(f"IPC route_trace error: {e}")
|
||||||
return {"success": False, "message": str(e)}
|
return {"success": False, "message": str(e)}
|
||||||
|
|
||||||
|
def _ipc_route_arc_trace(self, params: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""IPC handler for route_arc_trace - adds copper arc with real-time UI update"""
|
||||||
|
try:
|
||||||
|
start = params.get("start", {})
|
||||||
|
mid = params.get("mid", {})
|
||||||
|
end = params.get("end", {})
|
||||||
|
layer = params.get("layer", "F.Cu")
|
||||||
|
width = params.get("width", 0.25)
|
||||||
|
net = params.get("net")
|
||||||
|
|
||||||
|
start_x = start.get("x", 0)
|
||||||
|
start_y = start.get("y", 0)
|
||||||
|
mid_x = mid.get("x", 0)
|
||||||
|
mid_y = mid.get("y", 0)
|
||||||
|
end_x = end.get("x", 0)
|
||||||
|
end_y = end.get("y", 0)
|
||||||
|
|
||||||
|
if not hasattr(self.ipc_board_api, "add_arc_track"):
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"message": "IPC backend does not support arc track on this installation",
|
||||||
|
}
|
||||||
|
|
||||||
|
success = self.ipc_board_api.add_arc_track(
|
||||||
|
start_x=start_x,
|
||||||
|
start_y=start_y,
|
||||||
|
mid_x=mid_x,
|
||||||
|
mid_y=mid_y,
|
||||||
|
end_x=end_x,
|
||||||
|
end_y=end_y,
|
||||||
|
width=width,
|
||||||
|
layer=layer,
|
||||||
|
net_name=net,
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": success,
|
||||||
|
"message": (
|
||||||
|
"Added arc trace (visible in KiCAD UI)"
|
||||||
|
if success
|
||||||
|
else "Failed to add arc trace"
|
||||||
|
),
|
||||||
|
"arc": {
|
||||||
|
"start": {"x": start_x, "y": start_y, "unit": "mm"},
|
||||||
|
"mid": {"x": mid_x, "y": mid_y, "unit": "mm"},
|
||||||
|
"end": {"x": end_x, "y": end_y, "unit": "mm"},
|
||||||
|
"layer": layer,
|
||||||
|
"width": width,
|
||||||
|
"net": net,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"IPC route_arc_trace error: {e}")
|
||||||
|
return {"success": False, "message": str(e)}
|
||||||
|
|
||||||
def _ipc_add_via(self, params: Dict[str, Any]) -> Dict[str, Any]:
|
def _ipc_add_via(self, params: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
"""IPC handler for add_via - adds via with real-time UI update"""
|
"""IPC handler for add_via - adds via with real-time UI update"""
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -63,6 +63,49 @@ export function registerRoutingTools(server: McpServer, callKicadScript: Functio
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Route arc trace tool
|
||||||
|
server.tool(
|
||||||
|
"route_arc_trace",
|
||||||
|
"Route a copper arc trace defined by start/mid/end points. Uses true PCB arc primitives when available.",
|
||||||
|
{
|
||||||
|
start: z
|
||||||
|
.object({
|
||||||
|
x: z.number(),
|
||||||
|
y: z.number(),
|
||||||
|
unit: z.string().optional(),
|
||||||
|
})
|
||||||
|
.describe("Arc start position"),
|
||||||
|
mid: z
|
||||||
|
.object({
|
||||||
|
x: z.number(),
|
||||||
|
y: z.number(),
|
||||||
|
unit: z.string().optional(),
|
||||||
|
})
|
||||||
|
.describe("A point on arc midpoint"),
|
||||||
|
end: z
|
||||||
|
.object({
|
||||||
|
x: z.number(),
|
||||||
|
y: z.number(),
|
||||||
|
unit: z.string().optional(),
|
||||||
|
})
|
||||||
|
.describe("Arc end position"),
|
||||||
|
layer: z.string().describe("PCB layer"),
|
||||||
|
width: z.number().describe("Trace width in mm"),
|
||||||
|
net: z.string().optional().describe("Net name"),
|
||||||
|
},
|
||||||
|
async (args: any) => {
|
||||||
|
const result = await callKicadScript("route_arc_trace", args);
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: JSON.stringify(result, null, 2),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// Add via tool
|
// Add via tool
|
||||||
server.tool(
|
server.tool(
|
||||||
"add_via",
|
"add_via",
|
||||||
|
|||||||
Reference in New Issue
Block a user