Add KiCad cad-bridge JSON and an action plugin for finding focus.
Write pinscope-findings.json from the report, keep symbol uuid and child-sheet on the graph, and let the pcbnew plugin jump to a footprint or show the schematic uuid when IPC focus is unavailable. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
"""E2 cad-bridge JSON and KiCad plugin focus helpers."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from backend.pinscopex.cad_bridge import annotate_findings_cad, build_cad_bridge
|
||||
from backend.pinscopex.models import CadIndexEntry, Finding, ValidationReport
|
||||
from backend.pinscopex.parsers_kicad import kicad_part_fields, parse_kicad
|
||||
from plugins.kicad.focus import find_bridge_file, focus_target, load_bridge
|
||||
|
||||
|
||||
def _f(**kwargs) -> Finding:
|
||||
defaults = dict(designator="U3", finding="mux", status="WARNING")
|
||||
defaults.update(kwargs)
|
||||
return Finding(**defaults)
|
||||
|
||||
|
||||
def test_bridge_exports_version_and_strips_pin_prefix():
|
||||
report = ValidationReport(
|
||||
project="p", timestamp="t",
|
||||
findings=[_f(
|
||||
finding_id="U3-001", rule_id="PS-MUX-001",
|
||||
pins=["U3.12"], cad_sheet="power.kicad_sch",
|
||||
cad_uuid="aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
|
||||
net="UART5_TX",
|
||||
)],
|
||||
summary={"total": 1},
|
||||
)
|
||||
payload = build_cad_bridge(report, "proj-1", url_base="https://app/report")
|
||||
assert payload["version"] == 1
|
||||
assert payload["project_id"] == "proj-1"
|
||||
row = payload["findings"][0]
|
||||
assert row["pins"] == ["12"]
|
||||
assert row["sheet"] == "power.kicad_sch"
|
||||
assert row["uuid"].startswith("aaaa")
|
||||
assert row["severity"] == "warning"
|
||||
assert "finding=U3-001" in row["url"]
|
||||
assert row["target"] == "sch"
|
||||
|
||||
|
||||
def test_missing_uuid_stays_empty_and_pcb_rule_targets_board():
|
||||
report = ValidationReport(
|
||||
project="p", timestamp="t",
|
||||
findings=[_f(rule_id="PS-PLC-001", pins=["1"], status="ERROR")],
|
||||
summary={"total": 1},
|
||||
)
|
||||
row = build_cad_bridge(report, "x")["findings"][0]
|
||||
assert row["uuid"] == ""
|
||||
assert row["sheet"] == ""
|
||||
assert row["target"] == "pcb"
|
||||
assert row["severity"] == "error"
|
||||
|
||||
|
||||
def test_annotate_does_not_overwrite_existing_cad_fields():
|
||||
idx = {"U3": CadIndexEntry(uuid="from-index", sheet="child.kicad_sch")}
|
||||
f = _f(cad_uuid="already", cad_sheet=None)
|
||||
annotate_findings_cad([f], idx)
|
||||
assert f.cad_uuid == "already"
|
||||
assert f.cad_sheet == "child.kicad_sch"
|
||||
|
||||
|
||||
def test_kicad_sch_fields_include_uuid_and_child_sheet(tmp_path: Path):
|
||||
from tests.test_kicad_parser import _resistor, _sch
|
||||
|
||||
child = tmp_path / "analog.kicad_sch"
|
||||
child.write_text(_sch(
|
||||
_resistor("U3", "MCU"),
|
||||
"""
|
||||
(global_label "GND" (at 0 3.81 0) (uuid "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"))
|
||||
""",
|
||||
))
|
||||
root = tmp_path / "root.kicad_sch"
|
||||
root.write_text(_sch(
|
||||
_resistor("R1", "10k"),
|
||||
"""
|
||||
(global_label "GND" (at 0 3.81 0) (uuid "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"))
|
||||
(sheet
|
||||
(at 50 0)
|
||||
(size 20 20)
|
||||
(property "Sheetname" "Analog" (at 50 0 0) (effects (font (size 1.27 1.27))))
|
||||
(property "Sheetfile" "analog.kicad_sch" (at 50 0 0) (effects (font (size 1.27 1.27))))
|
||||
)
|
||||
""",
|
||||
))
|
||||
parse_kicad(root)
|
||||
fields = kicad_part_fields(root)
|
||||
assert fields["U3"]["cad_sheet"] == "analog.kicad_sch"
|
||||
assert fields["R1"]["cad_sheet"] == "root.kicad_sch"
|
||||
assert fields["U3"].get("cad_uuid")
|
||||
assert fields["U3"]["cad_uuid"] != fields["R1"]["cad_uuid"]
|
||||
|
||||
|
||||
def test_plugin_finds_bridge_and_pcb_target(tmp_path: Path):
|
||||
(tmp_path / "pinscope-findings.json").write_text(
|
||||
'{"version":1,"project_id":"p","findings":[]}\n'
|
||||
)
|
||||
nested = tmp_path / "board"
|
||||
nested.mkdir()
|
||||
found = find_bridge_file(nested / "x.kicad_pcb")
|
||||
assert found == tmp_path / "pinscope-findings.json"
|
||||
assert load_bridge(found)["version"] == 1
|
||||
t = focus_target({"ref": "U1", "rule_id": "PS-PLC-001", "uuid": "x", "sheet": ""})
|
||||
assert t["kind"] == "pcb" and t["ref"] == "U1"
|
||||
missing = tmp_path / "nowhere"
|
||||
missing.mkdir()
|
||||
assert find_bridge_file(missing, max_up=0) is None
|
||||
@@ -137,12 +137,13 @@ _LIB_R = """
|
||||
|
||||
|
||||
def _resistor(ref: str, value: str, x: float = 0, y: float = 0) -> str:
|
||||
uid = "aaaaaaaa-aaaa-aaaa-aaaa-" + ref.encode().hex()[:12].ljust(12, "0")
|
||||
return f"""
|
||||
(symbol
|
||||
(lib_id "Device:R")
|
||||
(at {x} {y} 0)
|
||||
(unit 1)
|
||||
(uuid "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa")
|
||||
(uuid "{uid}")
|
||||
(property "Reference" "{ref}" (at 0 0 0) (effects (font (size 1.27 1.27))))
|
||||
(property "Value" "{value}" (at 0 0 0) (effects (font (size 1.27 1.27))))
|
||||
(pin "1" (uuid "p1"))
|
||||
|
||||
Reference in New Issue
Block a user