Say which connections were not routed
freeroute exited 0 and said nothing when it dropped connections, which cost real diagnosis time: on rc4 two simple two-pin nets came back with no copper and no explanation. The grid now records each connection it could not route, the exact check adds each it rejects, and the CLI lists them on stderr (the SES is still written; the exit code stays 0, as the JAR's). A connection whose ends both sit in one zone of its net is listed apart, as left to the zone fill: freeroute does not route through a zone it cannot trust to be solid, but KiCad's fill often joins those pads anyway.
This commit is contained in:
@@ -59,7 +59,7 @@ class RouteResult:
|
||||
``routed_net_numbers`` are the nets that got any wire or via.
|
||||
"""
|
||||
|
||||
__slots__ = ("wires", "vias", "half_width", "unrouted", "connections")
|
||||
__slots__ = ("wires", "vias", "half_width", "unrouted", "connections", "connection_ends", "failed")
|
||||
|
||||
def __init__(self, half_width: int) -> None:
|
||||
self.wires: dict[int, list[tuple[int, list[IntPoint]]]] = {}
|
||||
@@ -69,6 +69,11 @@ class RouteResult:
|
||||
self.unrouted = 0
|
||||
#: the same copper per routed connection: (net_no, wires, vias)
|
||||
self.connections: list[tuple[int, list[tuple[int, list[IntPoint]]], list[IntPoint]]] = []
|
||||
#: what each routed connection joins, and the zone both ends sit in
|
||||
#: (or ""), parallel to ``connections``
|
||||
self.connection_ends: list[tuple[str, str, str]] = []
|
||||
#: connections left unrouted: (net_no, one end, the other end)
|
||||
self.failed: list[tuple[int, str, str]] = []
|
||||
|
||||
def add_wire(self, net_no: int, layer: int, path: list[IntPoint]) -> None:
|
||||
self.wires.setdefault(net_no, []).append((layer, path))
|
||||
@@ -523,6 +528,16 @@ class GridRouter:
|
||||
covered = sum((*cell, zone.layer) in self._keepout for cell in cells)
|
||||
return covered >= coverage * len(cells)
|
||||
|
||||
def _left_to_zone(self, conn) -> str:
|
||||
"""The zone both ends of an unrouted connection sit in, if any: its
|
||||
fill may well join them, even when it is not trusted for routing."""
|
||||
for zone in self._every_zone_by_net.get(conn.net_no, []):
|
||||
if any(_pin_touches_zone(p, zone) for p in conn.a_group[0]) and any(
|
||||
_pin_touches_zone(p, zone) for p in conn.b_group[0]
|
||||
):
|
||||
return zone.name or "zone"
|
||||
return ""
|
||||
|
||||
def _group_ends(self, group, net_no: int, conns) -> dict[Node, Pin | None]:
|
||||
"""End nodes of a group, each mapped to its pin (None for a zone cell)."""
|
||||
ends: dict[Node, Pin | None] = {}
|
||||
@@ -668,6 +683,7 @@ class GridRouter:
|
||||
def route(self) -> RouteResult:
|
||||
"""Route all ratsnest connections, using rip-up-and-retry across passes."""
|
||||
conns = self._build_connections()
|
||||
self._conns = conns
|
||||
if not conns:
|
||||
return RouteResult(self.half_width)
|
||||
|
||||
@@ -709,6 +725,10 @@ class GridRouter:
|
||||
def _build_connections(self) -> list[_Connection]:
|
||||
pins_by_net = self._pins_by_net()
|
||||
zones_by_net: dict[int, list] = {}
|
||||
self._every_zone_by_net: dict[int, list] = {}
|
||||
for zone in self.board.get_conduction_areas():
|
||||
if zone.layer in self.layers and zone.net_nos:
|
||||
self._every_zone_by_net.setdefault(zone.net_nos[0], []).append(zone)
|
||||
for zone in self.board.get_conduction_areas():
|
||||
if zone.layer in self.layers and zone.net_nos and self._zone_is_solid(zone):
|
||||
zones_by_net.setdefault(zone.net_nos[0], []).append(zone)
|
||||
@@ -732,9 +752,10 @@ class GridRouter:
|
||||
by_net.setdefault(net_no, []).append(pin)
|
||||
return by_net
|
||||
|
||||
def _snapshot(self, conns: list[_Connection]) -> list[tuple[int, list, list]]:
|
||||
def _snapshot(self, conns: list[_Connection]) -> list[tuple]:
|
||||
return [
|
||||
(c.net_no, [(layer, list(pts)) for layer, pts in c.wires], list(c.vias))
|
||||
(c.net_no, [(layer, list(pts)) for layer, pts in c.wires], list(c.vias),
|
||||
(*_ends_of(c), self._left_to_zone(c)))
|
||||
for c in conns
|
||||
if c.routed
|
||||
]
|
||||
@@ -742,12 +763,19 @@ class GridRouter:
|
||||
def _result_from(self, snapshot, unrouted: int) -> RouteResult:
|
||||
result = RouteResult(self.half_width)
|
||||
result.unrouted = unrouted
|
||||
for net_no, wires, vias in snapshot:
|
||||
for net_no, wires, vias, ends in snapshot:
|
||||
for layer, points in wires:
|
||||
result.add_wire(net_no, layer, points)
|
||||
for location in vias:
|
||||
result.add_via(net_no, location)
|
||||
result.connections.append((net_no, wires, vias))
|
||||
result.connection_ends.append(ends)
|
||||
routed = {ends[:2] + (net_no,) for net_no, _w, _v, ends in snapshot}
|
||||
result.failed = [
|
||||
(c.net_no, *_ends_of(c), self._left_to_zone(c))
|
||||
for c in self._conns
|
||||
if _ends_of(c) + (c.net_no,) not in routed
|
||||
]
|
||||
return result
|
||||
|
||||
|
||||
@@ -755,6 +783,20 @@ def _first(items):
|
||||
return items[0] if items else None
|
||||
|
||||
|
||||
def _group_label(group) -> str:
|
||||
pins, zones = group
|
||||
parts = [f"{p.component_ref}-{p.name}" for p in pins[:3]]
|
||||
if len(pins) > 3:
|
||||
parts.append(f"+{len(pins) - 3} pins")
|
||||
parts += [f"zone {z.name}" for z in zones[:1]]
|
||||
return "/".join(parts) or "?"
|
||||
|
||||
|
||||
def _ends_of(conn) -> tuple[str, str]:
|
||||
"""A readable description of what a connection joins."""
|
||||
return _group_label(conn.a_group), _group_label(conn.b_group)
|
||||
|
||||
|
||||
def _pin_touches_zone(pin: Pin, zone) -> bool:
|
||||
"""A pad on the zone's layer whose centre or a corner lies in the zone."""
|
||||
if zone.layer not in pin.layers or zone.polygon is None:
|
||||
|
||||
Reference in New Issue
Block a user