feat: add find_orphaned_wires schematic analysis tool
Detects wire segments with at least one dangling endpoint — an endpoint
not connected to a component pin, net label, or another wire. These
cause ERC 'wire end unconnected' violations and are a common symptom of
incomplete routing or stray stub wires.
Algorithm uses exact KiCad IU (10 000 IU/mm) coordinate matching,
consistent with wire_connectivity.py:
1. Build an endpoint-frequency map for all wires (IU precision)
2. Collect anchored IU points: component pins (via PinLocator),
net labels / global_labels, power symbol pins
(via _parse_virtual_connections)
3. An endpoint is dangling when it is touched by exactly one wire AND
is not an anchored point; the containing wire is reported
Does not require the KiCad UI to be running.
Changes:
python/commands/schematic_analysis.py — find_orphaned_wires() function
python/kicad_interface.py — handler + route registration
python/schemas/tool_schemas.py — MCP schema entry
src/tools/schematic.ts — TypeScript server.tool() call
tests/test_schematic_analysis.py — 7 integration tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1360,4 +1360,36 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
// Find orphaned wires
|
||||
server.tool(
|
||||
"find_orphaned_wires",
|
||||
"Find wire segments with at least one dangling endpoint — not connected to a component pin, " +
|
||||
"net label, or another wire. Orphaned wires cause ERC 'wire end unconnected' errors. " +
|
||||
"Does not require the KiCad UI to be running.",
|
||||
{
|
||||
schematicPath: z.string().describe("Path to the .kicad_sch schematic file"),
|
||||
},
|
||||
async (args: { schematicPath: string }) => {
|
||||
const result = await callKicadScript("find_orphaned_wires", args);
|
||||
if (result.success) {
|
||||
const wires: any[] = result.orphaned_wires || [];
|
||||
if (wires.length === 0) {
|
||||
return { content: [{ type: "text", text: "No orphaned wires found." }] };
|
||||
}
|
||||
const lines: string[] = [`Found ${wires.length} orphaned wire(s):\n`];
|
||||
wires.slice(0, 50).forEach((w: any) => {
|
||||
const dangling = w.dangling_ends.map((e: any) => `(${e.x}, ${e.y})`).join(", ");
|
||||
lines.push(
|
||||
` wire (${w.start.x}, ${w.start.y})→(${w.end.x}, ${w.end.y}) dangling end(s): ${dangling}`,
|
||||
);
|
||||
});
|
||||
if (wires.length > 50) lines.push(` ... and ${wires.length - 50} more`);
|
||||
return { content: [{ type: "text", text: lines.join("\n") }] };
|
||||
}
|
||||
return {
|
||||
content: [{ type: "text", text: `Failed: ${result.message || "Unknown error"}` }],
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user