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.
98 lines
2.8 KiB
Python
98 lines
2.8 KiB
Python
"""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,
|
|
)
|