feat: add run_erc tool for schematic electrical rules checking
This commit is contained in:
@@ -378,6 +378,7 @@ class KiCADInterface:
|
|||||||
"connect_passthrough": self._handle_connect_passthrough,
|
"connect_passthrough": self._handle_connect_passthrough,
|
||||||
"get_schematic_pin_locations": self._handle_get_schematic_pin_locations,
|
"get_schematic_pin_locations": self._handle_get_schematic_pin_locations,
|
||||||
"get_net_connections": self._handle_get_net_connections,
|
"get_net_connections": self._handle_get_net_connections,
|
||||||
|
"run_erc": self._handle_run_erc,
|
||||||
"generate_netlist": self._handle_generate_netlist,
|
"generate_netlist": self._handle_generate_netlist,
|
||||||
"list_schematic_libraries": self._handle_list_schematic_libraries,
|
"list_schematic_libraries": self._handle_list_schematic_libraries,
|
||||||
"export_schematic_pdf": self._handle_export_schematic_pdf,
|
"export_schematic_pdf": self._handle_export_schematic_pdf,
|
||||||
@@ -1367,6 +1368,88 @@ class KiCADInterface:
|
|||||||
logger.error(f"Error getting net connections: {str(e)}")
|
logger.error(f"Error getting net connections: {str(e)}")
|
||||||
return {"success": False, "message": str(e)}
|
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")
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
import os
|
||||||
|
|
||||||
|
try:
|
||||||
|
schematic_path = params.get("schematicPath")
|
||||||
|
if not schematic_path or not os.path.exists(schematic_path):
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"message": "Schematic file not found",
|
||||||
|
"errorDetails": f"Path does not exist: {schematic_path}",
|
||||||
|
}
|
||||||
|
|
||||||
|
kicad_cli = self.design_rule_commands._find_kicad_cli()
|
||||||
|
if not kicad_cli:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"message": "kicad-cli not found",
|
||||||
|
"errorDetails": "Install KiCAD 8.0+ or add kicad-cli to PATH.",
|
||||||
|
}
|
||||||
|
|
||||||
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as tmp:
|
||||||
|
json_output = tmp.name
|
||||||
|
|
||||||
|
try:
|
||||||
|
cmd = [kicad_cli, "sch", "erc", "--format", "json", "--output", json_output, schematic_path]
|
||||||
|
logger.info(f"Running ERC command: {' '.join(cmd)}")
|
||||||
|
|
||||||
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
|
||||||
|
|
||||||
|
if result.returncode != 0:
|
||||||
|
logger.error(f"ERC command failed: {result.stderr}")
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"message": "ERC command failed",
|
||||||
|
"errorDetails": result.stderr,
|
||||||
|
}
|
||||||
|
|
||||||
|
with open(json_output, "r", encoding="utf-8") as f:
|
||||||
|
erc_data = json.load(f)
|
||||||
|
|
||||||
|
violations = []
|
||||||
|
severity_counts = {"error": 0, "warning": 0, "info": 0}
|
||||||
|
|
||||||
|
for v in erc_data.get("violations", []):
|
||||||
|
vseverity = v.get("severity", "error")
|
||||||
|
items = v.get("items", [])
|
||||||
|
loc = {}
|
||||||
|
if items and "pos" in items[0]:
|
||||||
|
loc = {"x": items[0]["pos"].get("x", 0), "y": items[0]["pos"].get("y", 0)}
|
||||||
|
violations.append({
|
||||||
|
"type": v.get("type", "unknown"),
|
||||||
|
"severity": vseverity,
|
||||||
|
"message": v.get("description", ""),
|
||||||
|
"location": loc,
|
||||||
|
})
|
||||||
|
if vseverity in severity_counts:
|
||||||
|
severity_counts[vseverity] += 1
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"message": f"ERC complete: {len(violations)} violation(s)",
|
||||||
|
"summary": {
|
||||||
|
"total": len(violations),
|
||||||
|
"by_severity": severity_counts,
|
||||||
|
},
|
||||||
|
"violations": violations,
|
||||||
|
}
|
||||||
|
|
||||||
|
finally:
|
||||||
|
if os.path.exists(json_output):
|
||||||
|
os.unlink(json_output)
|
||||||
|
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
return {"success": False, "message": "ERC timed out after 120 seconds"}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error running ERC: {str(e)}")
|
||||||
|
return {"success": False, "message": str(e)}
|
||||||
|
|
||||||
def _handle_generate_netlist(self, params):
|
def _handle_generate_netlist(self, params):
|
||||||
"""Generate netlist from schematic"""
|
"""Generate netlist from schematic"""
|
||||||
logger.info("Generating netlist from schematic")
|
logger.info("Generating netlist from schematic")
|
||||||
|
|||||||
@@ -1513,6 +1513,21 @@ SCHEMATIC_TOOLS = [
|
|||||||
"required": ["schematicPath", "sourceRef", "targetRef"]
|
"required": ["schematicPath", "sourceRef", "targetRef"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "run_erc",
|
||||||
|
"title": "Run Electrical Rules Check (ERC)",
|
||||||
|
"description": "Runs the KiCAD Electrical Rules Check (ERC) on a schematic via kicad-cli and returns all violations with type, severity, and location. Use this to verify the schematic is electrically correct before generating a netlist or exporting.",
|
||||||
|
"inputSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"schematicPath": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Path to the .kicad_sch schematic file"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["schematicPath"]
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "generate_netlist",,
|
"name": "generate_netlist",,
|
||||||
"title": "Generate Netlist",
|
"title": "Generate Netlist",
|
||||||
|
|||||||
@@ -445,6 +445,41 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Run Electrical Rules Check (ERC)
|
||||||
|
server.tool(
|
||||||
|
"run_erc",
|
||||||
|
"Runs the KiCAD Electrical Rules Check (ERC) on a schematic and returns all violations. Use after wiring to verify the schematic before generating a netlist.",
|
||||||
|
{
|
||||||
|
schematicPath: z.string().describe("Path to the .kicad_sch schematic file"),
|
||||||
|
},
|
||||||
|
async (args: { schematicPath: string }) => {
|
||||||
|
const result = await callKicadScript("run_erc", args);
|
||||||
|
if (result.success) {
|
||||||
|
const violations: any[] = result.violations || [];
|
||||||
|
const lines: string[] = [`ERC result: ${violations.length} violation(s)`];
|
||||||
|
if (result.summary?.by_severity) {
|
||||||
|
const s = result.summary.by_severity;
|
||||||
|
lines.push(` Errors: ${s.error ?? 0} Warnings: ${s.warning ?? 0} Info: ${s.info ?? 0}`);
|
||||||
|
}
|
||||||
|
if (violations.length > 0) {
|
||||||
|
lines.push("");
|
||||||
|
violations.slice(0, 30).forEach((v: any, i: number) => {
|
||||||
|
const loc = v.location && (v.location.x !== undefined) ? ` @ (${v.location.x}, ${v.location.y})` : "";
|
||||||
|
lines.push(`${i + 1}. [${v.severity}] ${v.message}${loc}`);
|
||||||
|
});
|
||||||
|
if (violations.length > 30) {
|
||||||
|
lines.push(`... and ${violations.length - 30} more`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
content: [{ type: "text", text: `ERC failed: ${result.message || "Unknown error"}${result.errorDetails ? "\n" + result.errorDetails : ""}` }],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// Generate netlist
|
// Generate netlist
|
||||||
server.tool(
|
server.tool(
|
||||||
"generate_netlist",
|
"generate_netlist",
|
||||||
|
|||||||
Reference in New Issue
Block a user