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.
84 lines
2.5 KiB
Python
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]
|