Add MODE=pcb layout exam job with AI and deterministic checks.
Parallel pcb_status pipeline: parse board, classify domains/groups, inventory traces, PE-LAY/PLC/SI/DRT checks, per-IC datasheet review. Findings merge into the report UI. No auto-place or pcbnew write-back.
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
"""Trace / bus inventory from a LayoutGraph. Numbers only, no findings."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from collections import defaultdict
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from backend.periscopex.models import DesignGraph, LayoutGraph, NetType
|
||||
from backend.periscopex.si_check import net_length_mm, partner_net
|
||||
|
||||
|
||||
_BUS_RE = re.compile(r"^(.*?)(\d+)$")
|
||||
|
||||
|
||||
class PcbNetInventory(BaseModel):
|
||||
name: str
|
||||
length_mm: float = 0.0
|
||||
via_count: int = 0
|
||||
layers: list[str] = []
|
||||
width_min_mm: float | None = None
|
||||
width_max_mm: float | None = None
|
||||
pair: str | None = None
|
||||
bus: str | None = None
|
||||
net_type: str | None = None
|
||||
z0_ohm: float | None = None
|
||||
|
||||
|
||||
class PcbInventoryReport(BaseModel):
|
||||
nets: list[PcbNetInventory] = []
|
||||
domains: list[str] = []
|
||||
group_count: int = 0
|
||||
|
||||
|
||||
def _bus_name(net: str) -> str | None:
|
||||
m = _BUS_RE.match(net.split("/")[-1].replace(".", "_"))
|
||||
if not m:
|
||||
return None
|
||||
prefix, digits = m.group(1), m.group(2)
|
||||
if not prefix or len(digits) > 3:
|
||||
return None
|
||||
return prefix.rstrip("_") or None
|
||||
|
||||
|
||||
def build_pcb_inventory(
|
||||
layout: LayoutGraph,
|
||||
graph: DesignGraph | None = None,
|
||||
*,
|
||||
domain_ids: list[str] | None = None,
|
||||
group_count: int = 0,
|
||||
z0_by_net: dict[str, float] | None = None,
|
||||
) -> PcbInventoryReport:
|
||||
names: set[str] = set()
|
||||
layers: dict[str, set[str]] = defaultdict(set)
|
||||
widths: dict[str, list[float]] = defaultdict(list)
|
||||
vias: dict[str, int] = defaultdict(int)
|
||||
for s in layout.segments:
|
||||
if not s.net:
|
||||
continue
|
||||
names.add(s.net)
|
||||
if s.layer:
|
||||
layers[s.net].add(s.layer)
|
||||
if s.width > 0:
|
||||
widths[s.net].append(s.width)
|
||||
for v in layout.vias:
|
||||
if v.net:
|
||||
names.add(v.net)
|
||||
vias[v.net] += 1
|
||||
z0_by_net = z0_by_net or {}
|
||||
rows: list[PcbNetInventory] = []
|
||||
for name in sorted(names):
|
||||
w = widths.get(name) or []
|
||||
sch = graph.nets.get(name) if graph else None
|
||||
nt = sch.net_type.value if sch and isinstance(sch.net_type, NetType) else (
|
||||
str(sch.net_type) if sch and sch.net_type else None
|
||||
)
|
||||
partner = partner_net(name)
|
||||
if partner and partner not in names:
|
||||
partner = None
|
||||
rows.append(PcbNetInventory(
|
||||
name=name,
|
||||
length_mm=round(net_length_mm(layout, name), 3),
|
||||
via_count=vias.get(name, 0),
|
||||
layers=sorted(layers.get(name, ())),
|
||||
width_min_mm=min(w) if w else None,
|
||||
width_max_mm=max(w) if w else None,
|
||||
pair=partner,
|
||||
bus=_bus_name(name),
|
||||
net_type=nt,
|
||||
z0_ohm=z0_by_net.get(name),
|
||||
))
|
||||
return PcbInventoryReport(
|
||||
nets=rows,
|
||||
domains=list(domain_ids or []),
|
||||
group_count=group_count,
|
||||
)
|
||||
Reference in New Issue
Block a user