Tighten PCB findings: actions, stitch gating, real copper.

Fill action on every finding and show it in the report. Stitch/return only
on large HS/power nets. Parse stackup thickness, arcs, and vias from the
board. IC courtyard pad copper is not PE-PLC-004 RULE. Recommended RC/cap
stay REVIEW.
This commit is contained in:
2026-09-20 08:58:54 +02:00
parent 1e379f6042
commit f63c1c3411
9 changed files with 326 additions and 59 deletions
+35 -3
View File
@@ -5,6 +5,7 @@ No SI/DRC. Schema validation stays complete without this file.
from __future__ import annotations
import re
from pathlib import Path
from backend.periscopex.models import (
@@ -29,6 +30,24 @@ from backend.periscopex.parsers_kicad import (
)
def _copper_thickness_token(raw: object) -> float | None:
"""Parse KiCad stackup thickness; units mm/um/mil. Never default 1 oz."""
if isinstance(raw, (int, float)) and not isinstance(raw, bool):
v = float(raw)
return v if v > 0 else None
s = str(raw or "").strip().lower().replace(" ", "")
m = re.match(r"^([0-9]*\.?[0-9]+)(mm|um|µm|mil)?", s)
if not m:
return None
n = float(m.group(1))
unit = m.group(2) or "mm"
if unit in {"um", "µm"}:
n = n / 1000.0
elif unit == "mil":
n = n * 0.0254
return n if n > 0 else None
def _xy(node: object, name: str) -> tuple[float, float]:
k = _kid(node, name)
if not k or len(k) < 3:
@@ -128,7 +147,11 @@ def _parse_stackup(tree: object) -> LayoutStackup | None:
name = str(layer[1]) if len(layer) > 1 and not isinstance(layer[1], list) else ""
kind = _layer_type(layer)
thick = _kid(layer, "thickness")
height = _fnum(thick[1]) if thick and len(thick) > 1 else None
height = None
if thick and len(thick) > 1:
height = _copper_thickness_token(thick[1])
if height is None and len(thick) > 2:
height = _copper_thickness_token(f"{thick[1]}{thick[2]}")
if kind == "copper" or name.endswith(".Cu"):
if name:
copper.append(name)
@@ -147,9 +170,9 @@ def _parse_stackup(tree: object) -> LayoutStackup | None:
er=er,
height_mm=height,
))
if len(copper) < 2 or len(dielectrics) != len(copper) - 1:
return None
t = thicknesses[0] if thicknesses else None
if not copper and t is None:
return None
return LayoutStackup(
copper_layers=copper,
dielectrics=dielectrics,
@@ -291,6 +314,15 @@ def parse_kicad_pcb(path: str | Path) -> LayoutGraph:
net=_net_name(node, nets),
))
continue
if tag == "arc":
segments.append(LayoutSegment(
start=_xy(node, "start"),
end=_xy(node, "end"),
width=_fnum(_val(node, "width") or 0),
layer=_val(node, "layer"),
net=_net_name(node, nets),
))
continue
if tag == "via":
drill_el = _kid(node, "drill")
drill = _fnum(drill_el[1]) if drill_el and len(drill_el) > 1 else None