Files
rpmroute/src/freeroute/ses/model.py
T
rsp2k cdb3c7b568 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.
2026-09-27 12:34:20 -06:00

84 lines
2.5 KiB
Python

"""In-memory routing result — the input the SES writer serializes.
The maze router (a later stage) populates a :class:`RoutingResult`; the SES
writer turns it into the ``(routes (network_out ...))`` scope. Coordinates and
widths are in DSN units (the same units the DSN was parsed in); the writer
rounds them to integers, matching FreeRouting's ``SesWriter`` which emits
integer session coordinates.
"""
from __future__ import annotations
from dataclasses import dataclass, field
__all__ = ["RoutedWire", "RoutedVia", "RoutedNet", "RoutingResult"]
@dataclass
class RoutedWire:
"""A routed trace segment on one layer.
``coords`` is a flat ``[x1, y1, x2, y2, ...]`` list (DSN units); ``width`` is
the full trace width (DSN units). ``fixed`` maps to an emitted
``(type fix)`` marker when set.
"""
layer: str
width: float
coords: list[float]
fixed: bool = False
@dataclass
class RoutedVia:
"""A routed via at ``(x, y)`` (DSN units) using a named padstack."""
padstack: str
x: float
y: float
fixed: bool = False
@dataclass
class RoutedNet:
"""The routed wires and vias belonging to one net."""
name: str
wires: list[RoutedWire] = field(default_factory=list)
vias: list[RoutedVia] = field(default_factory=list)
@property
def has_items(self) -> bool:
return bool(self.wires or self.vias)
@dataclass
class RoutingResult:
"""The complete routing output for a board, grouped by net."""
nets: list[RoutedNet] = field(default_factory=list)
#: connections the router could not make: (net name, one end, other end,
#: the zone both ends sit in or "") -- a zone's fill may join the latter
unrouted: list[tuple[str, str, str, str]] = field(default_factory=list)
def net(self, name: str, *, create: bool = False) -> RoutedNet | None:
"""Return the :class:`RoutedNet` with ``name`` (creating it if asked)."""
for n in self.nets:
if n.name == name:
return n
if create:
n = RoutedNet(name=name)
self.nets.append(n)
return n
return None
def add_wire(self, net_name: str, wire: RoutedWire) -> None:
self.net(net_name, create=True).wires.append(wire)
def add_via(self, net_name: str, via: RoutedVia) -> None:
self.net(net_name, create=True).vias.append(via)
def routed_nets(self) -> list[RoutedNet]:
"""Nets that actually carry routed geometry (in insertion order)."""
return [n for n in self.nets if n.has_items]