Add /api/library datasheet import and component GET/PUT with no exam. Reorganize pytest into datasheet, library, schematic, PCB, and AF+AI. Document Rust criteria (none chosen; no rustup) and coding conformity.
127 lines
4.4 KiB
Python
127 lines
4.4 KiB
Python
"""Partial PCB: footprints with few tracks still parse and run MODE=pcb checks."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
from backend.periscopex.models import (
|
|
Component,
|
|
ComponentType,
|
|
DesignGraph,
|
|
Net,
|
|
NetType,
|
|
)
|
|
from backend.periscopex.parsers_kicad_pcb import parse_kicad_pcb
|
|
from backend.periscopex.pcb_checks import run_pcb_checks
|
|
from backend.periscopex.pcb_net_match import check_pcb_net_match
|
|
from backend.periscopex.placement_check import check_placement
|
|
|
|
|
|
_PARTIAL = """(kicad_pcb (version 20240108) (generator pcbnew)
|
|
(net 0 "")
|
|
(net 1 "GND")
|
|
(net 2 "+3V3")
|
|
(net 3 "USB_DP")
|
|
(footprint "Package_SO:SOIC-8"
|
|
(layer "F.Cu")
|
|
(at 20 10 0)
|
|
(property "Reference" "U1" (at 0 0 0) (effects (font (size 1 1))))
|
|
(pad "1" smd rect (at -2 1) (size 1 0.6) (layers "F.Cu") (net 2 "+3V3"))
|
|
(pad "2" smd rect (at -2 -1) (size 1 0.6) (layers "F.Cu") (net 1 "GND"))
|
|
(pad "3" smd rect (at 2 1) (size 1 0.6) (layers "F.Cu") (net 3 "USB_DP"))
|
|
)
|
|
(footprint "Capacitor_SMD:C_0603"
|
|
(layer "F.Cu")
|
|
(at 40 30 0)
|
|
(property "Reference" "C1" (at 0 0 0) (effects (font (size 1 1))))
|
|
(pad "1" smd rect (at -0.5 0) (size 0.8 0.9) (layers "F.Cu") (net 2 "+3V3"))
|
|
(pad "2" smd rect (at 0.5 0) (size 0.8 0.9) (layers "F.Cu") (net 1 "GND"))
|
|
)
|
|
(segment (start 18 11) (end 19 11) (width 0.2) (layer "F.Cu") (net 2))
|
|
)
|
|
"""
|
|
|
|
|
|
def test_parse_footprints_with_few_tracks(tmp_path: Path):
|
|
p = tmp_path / "partial.kicad_pcb"
|
|
p.write_text(_PARTIAL)
|
|
g = parse_kicad_pcb(p)
|
|
assert "U1" in g.footprints and "C1" in g.footprints
|
|
assert len(g.segments) == 1
|
|
assert g.segments[0].net == "+3V3"
|
|
assert len(g.vias) == 0
|
|
|
|
|
|
def _partial_graph() -> DesignGraph:
|
|
return DesignGraph(
|
|
components={
|
|
"U1": Component(
|
|
reference="U1", value="MCU", footprint="",
|
|
component_type=ComponentType.IC, mpn="X",
|
|
pins={"1": "+3V3", "2": "GND", "3": "USB_DP"},
|
|
),
|
|
"C1": Component(
|
|
reference="C1", value="100n", footprint="",
|
|
component_type=ComponentType.CAPACITOR, mpn="C",
|
|
pins={"1": "+3V3", "2": "GND"},
|
|
),
|
|
"R9": Component(
|
|
reference="R9", value="10k", footprint="",
|
|
component_type=ComponentType.RESISTOR, mpn="R",
|
|
pins={"1": "NRESET"},
|
|
),
|
|
},
|
|
nets={
|
|
"+3V3": Net(name="+3V3", net_type=NetType.POWER, pins=[]),
|
|
"GND": Net(name="GND", net_type=NetType.GROUND, pins=[]),
|
|
"USB_DP": Net(name="USB_DP", net_type=NetType.SIGNAL, pins=[]),
|
|
"NRESET": Net(name="NRESET", net_type=NetType.SIGNAL, pins=[]),
|
|
},
|
|
)
|
|
|
|
|
|
def test_run_pcb_checks_on_partial_layout_does_not_invent_tracks(tmp_path: Path):
|
|
p = tmp_path / "partial.kicad_pcb"
|
|
p.write_text(_PARTIAL)
|
|
layout = parse_kicad_pcb(p)
|
|
graph = _partial_graph()
|
|
findings = run_pcb_checks(graph, {}, layout)
|
|
lay = [f for f in findings if f.rule_id == "PE-LAY-004"]
|
|
assert lay, findings
|
|
assert lay[0].evidence_status == "INSUFFICIENT"
|
|
assert lay[0].status == "INFO"
|
|
assert all(f.rule_id != "PE-PLC-001" for f in findings)
|
|
unplaced = [f for f in findings if f.rule_id == "PE-LAY-002"]
|
|
assert any(f.designator == "R9" for f in unplaced)
|
|
|
|
|
|
def test_partial_board_via_is_not_a_pad(tmp_path: Path):
|
|
from tests.pcb.test_pcb_via_not_pad import _qfn24_pcb
|
|
|
|
layout = parse_kicad_pcb(_qfn24_pcb(tmp_path, board_vias=[(80.0, 80.0, "GND", 1)]))
|
|
u1 = layout.footprints["U1"]
|
|
assert len(u1.pads) == 24
|
|
assert len(layout.vias) == 1
|
|
via = layout.vias[0]
|
|
assert all(abs(p.x - via.x) > 0.01 or abs(p.y - via.y) > 0.01 for p in u1.pads)
|
|
|
|
|
|
def test_unplaced_ref_is_lay_002_not_invented_xy():
|
|
graph = _partial_graph()
|
|
from backend.periscopex.models import LayoutFootprint, LayoutGraph, LayoutPad
|
|
|
|
layout = LayoutGraph(
|
|
footprints={
|
|
"U1": LayoutFootprint(
|
|
reference="U1", x=0, y=0, layer="F.Cu",
|
|
pads=[LayoutPad(number="1", x=0, y=0, net="+3V3")],
|
|
),
|
|
},
|
|
)
|
|
findings = check_pcb_net_match(graph, layout)
|
|
assert any(f.rule_id == "PE-LAY-002" and f.designator == "C1" for f in findings)
|
|
assert check_placement(graph, {}, layout) == [] or all(
|
|
f.evidence_status == "INSUFFICIENT" or f.rule_id != "PE-PLC-001"
|
|
for f in check_placement(graph, {}, layout)
|
|
)
|