app
Adom Desktop - KiCad Bridge
Public Made by Adomby adom
Reference implementation of the KiCad bridge — multi-instance Python server, forward path via kicad-cli, reverse path via in-process plugin. Most complex of the three bundled bridges.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
"""Handler for run_drc command."""
import json
import subprocess
import tempfile
from pathlib import Path
def handle_run_drc(kicad_info: dict, args: dict) -> dict:
"""Run DRC on a PCB file using kicad-cli and parse JSON results."""
if not kicad_info.get("installed"):
return {
"success": False,
"error": "KiCad not installed",
"_hint": "Report to the user and ask them to install KiCad from https://www.kicad.org/download/, then retry.",
}
file_path = args.get("filePath", "")
if not file_path:
return {"success": False, "error": "No filePath specified"}
pcb_path = Path(file_path)
if not pcb_path.exists():
return {
"success": False,
"error": f"PCB file not found: {file_path}",
"_hint": "The path may exist on Docker but not on the Windows host. Use push_file to copy from Docker first, or verify the host-side path.",
}
cli_exe = kicad_info.get("kicad_cli_exe")
if not cli_exe or not Path(cli_exe).exists():
return {
"success": False,
"error": "kicad-cli not found",
"_hint": "Report to the user and ask them to reinstall KiCad — kicad-cli is missing from the installation.",
}
# Create a temp file for the JSON output
tmp_fd, output_path = tempfile.mkstemp(suffix=".json")
import os
os.close(tmp_fd)
try:
cmd = [
cli_exe,
"pcb", "drc",
"--format", "json",
"--severity-all",
"--all-track-errors",
"--output", output_path,
str(pcb_path),
]
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=120,
)
output_file = Path(output_path)
if not output_file.exists() or output_file.stat().st_size == 0:
return {
"success": False,
"error": f"DRC produced no output. stderr: {result.stderr}",
}
with open(output_path, "r", encoding="utf-8") as f:
drc_data = json.load(f)
violations = _parse_violations(drc_data)
total = len(violations)
errors = sum(1 for v in violations if v["severity"] == "error")
warnings = sum(1 for v in violations if v["severity"] == "warning")
return {
"success": True,
"output": f"DRC complete: {errors} error(s), {warnings} warning(s), {total} total",
"data": {
"violations": violations,
"summary": {
"total": total,
"errors": errors,
"warnings": warnings,
},
"source_file": str(pcb_path),
},
}
except subprocess.TimeoutExpired:
return {"success": False, "error": "DRC timed out after 120 seconds"}
except json.JSONDecodeError as e:
return {"success": False, "error": f"Failed to parse DRC JSON output: {e}"}
except Exception as e:
return {"success": False, "error": f"DRC failed: {e}"}
finally:
try:
Path(output_path).unlink(missing_ok=True)
except OSError:
pass
def _parse_violations(drc_data: dict) -> list:
"""Extract structured violation list from DRC JSON output."""
violations = []
for v in drc_data.get("violations", []):
violation = {
"type": v.get("type", "unknown"),
"severity": v.get("severity", "warning"),
"description": v.get("description", ""),
"items": [],
}
for item in v.get("items", []):
violation["items"].append({
"description": item.get("description", ""),
"pos": item.get("pos", {}),
})
violations.append(violation)
for v in drc_data.get("unconnected_items", []):
violation = {
"type": "unconnected_items",
"severity": v.get("severity", "error"),
"description": v.get("description", ""),
"items": [],
}
for item in v.get("items", []):
violation["items"].append({
"description": item.get("description", ""),
"pos": item.get("pos", {}),
})
violations.append(violation)
return violations