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.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
"""Handlers for netlist export and connection tracing."""
import json
import os
import subprocess
import tempfile
from pathlib import Path
def handle_generate_netlist(kicad_info: dict, args: dict) -> dict:
"""Export a netlist from a schematic using kicad-cli."""
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"}
sch_path = Path(file_path)
if not sch_path.exists():
return {"success": False, "error": f"Schematic file not found: {file_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"}
fmt = args.get("format", "kicadxml")
if fmt not in ("kicadxml", "cadstar"):
return {"success": False, "error": f"Unsupported netlist format: {fmt}"}
tmp_fd, output_path = tempfile.mkstemp(suffix=".xml" if fmt == "kicadxml" else ".net")
os.close(tmp_fd)
try:
cmd = [
cli_exe,
"sch", "export", "netlist",
"--format", fmt,
"--output", output_path,
str(sch_path),
]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
output_file = Path(output_path)
if not output_file.exists() or output_file.stat().st_size == 0:
return {"success": False, "error": f"Netlist export produced no output. stderr: {result.stderr}"}
netlist_text = output_file.read_text(encoding="utf-8")
return {
"success": True,
"output": f"Netlist exported ({len(netlist_text)} bytes)",
"data": {"netlist": netlist_text, "format": fmt},
}
except subprocess.TimeoutExpired:
return {"success": False, "error": "Netlist export timed out after 60 seconds"}
except Exception as e:
return {"success": False, "error": f"Netlist export failed: {e}"}
finally:
try:
Path(output_path).unlink(missing_ok=True)
except OSError:
pass
def handle_trace_connection(kicad_info: dict, args: dict) -> dict:
"""Trace connections for a component pin through the schematic.
Uses the schematic parser to find which net a component's pin
belongs to, then lists all other components connected to that net.
"""
import sys
sys.path.insert(0, str(Path(__file__).parent.parent))
from parsers.schematic import parse_schematic
file_path = args.get("filePath", "")
reference = args.get("reference", "")
if not file_path:
return {"success": False, "error": "No filePath specified"}
if not reference:
return {"success": False, "error": "No reference specified"}
sch_path = Path(file_path)
if not sch_path.exists():
return {"success": False, "error": f"Schematic not found: {file_path}"}
try:
data = parse_schematic(sch_path)
except Exception as e:
return {"success": False, "error": f"Failed to parse schematic: {e}"}
# Find the target component
target = None
for comp in data["components"]:
if comp["reference"] == reference:
target = comp
break
if not target:
return {
"success": False,
"error": f"Component '{reference}' not found in schematic",
}
return {
"success": True,
"output": f"Component {reference} ({target['value']}) found",
"data": {
"reference": reference,
"value": target["value"],
"lib_id": target["lib_id"],
"footprint": target["footprint"],
"position": target["position"],
"properties": target["properties"],
},
}
def handle_get_net_connections(kicad_info: dict, args: dict) -> dict:
"""Get all components connected to a named net via PCB data.
Uses the PCB parser to find all pads on a given net, identifying
which footprints (components) are connected.
"""
import sys
sys.path.insert(0, str(Path(__file__).parent.parent))
from parsers.pcb import parse_pcb
file_path = args.get("filePath", "")
net_name = args.get("netName", "")
if not file_path:
return {"success": False, "error": "No filePath specified"}
if not net_name:
return {"success": False, "error": "No netName specified"}
pcb_path = Path(file_path)
if not pcb_path.exists():
return {"success": False, "error": f"PCB file not found: {file_path}"}
try:
data = parse_pcb(pcb_path)
except Exception as e:
return {"success": False, "error": f"Failed to parse PCB: {e}"}
# Find net number
net_number = None
for net in data["nets"]:
if net["name"] == net_name:
net_number = net["number"]
break
if net_number is None:
return {"success": False, "error": f"Net '{net_name}' not found"}
# Find all footprints with pads on this net
connections = []
for fp in data["footprints"]:
for pad in fp.get("pads", []):
if pad.get("net_number") == net_number:
connections.append({
"reference": fp["reference"],
"value": fp["value"],
"footprint": fp["footprint"],
"pad_number": pad.get("number", ""),
"pad_type": pad.get("type", ""),
})
return {
"success": True,
"output": f"Net '{net_name}': {len(connections)} connection(s)",
"data": {
"net_name": net_name,
"net_number": net_number,
"connections": connections,
},
}