Count on a zone's connectivity only when no track can cross it
A zone outline is where copper may go, not where it ends up: on the rc4 sense board the board-wide GND pours break into islands around other nets' tracks, and treating every pad inside them as connected left 28 GND islands unjoined (63 unconnected against 42 without zones). Trust a zone only when a no-tracks rule area covers it on its layer, as the power stage's pours are covered; route the rest with tracks as before. rc4, exact engine, resolution 2: power stage 5 unconnected (JAR 12), sense board 41 (JAR 32), no DRC errors on either.
This commit is contained in:
@@ -506,6 +506,23 @@ class GridRouter:
|
||||
self._zone_cells[key] = sorted(set(nodes))
|
||||
return self._zone_cells[key]
|
||||
|
||||
def _zone_is_solid(self, zone, coverage: float = 0.95) -> bool:
|
||||
"""Whether a zone's fill can be trusted to be one piece of copper.
|
||||
|
||||
A zone outline is where copper may go, not where it will be: other
|
||||
nets' tracks through a board-wide pour break its fill into islands,
|
||||
and a pad inside the outline need not reach the rest of the net. It
|
||||
is safe to count on only when no track can cross it, which is what a
|
||||
no-tracks rule area over the zone guarantees (the rc4 power pours).
|
||||
Otherwise the zone is left out and its pads are routed with tracks.
|
||||
"""
|
||||
cells = [cell for tile in zone.tiles for cell in self._cells_in_box(tile.bounding_box(), 0)
|
||||
if zone.polygon.contains(self._cell_center(cell))]
|
||||
if not cells:
|
||||
return False
|
||||
covered = sum((*cell, zone.layer) in self._keepout for cell in cells)
|
||||
return covered >= coverage * len(cells)
|
||||
|
||||
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] = {}
|
||||
@@ -693,7 +710,7 @@ class GridRouter:
|
||||
pins_by_net = self._pins_by_net()
|
||||
zones_by_net: dict[int, list] = {}
|
||||
for zone in self.board.get_conduction_areas():
|
||||
if zone.layer in self.layers and zone.net_nos:
|
||||
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)
|
||||
conns: list[_Connection] = []
|
||||
nets = set(pins_by_net) | set(zones_by_net)
|
||||
|
||||
@@ -138,3 +138,13 @@ def test_a_pad_reaches_a_zone_under_a_no_tracks_area_by_a_via():
|
||||
if layer == f_cu: # no track runs inside the no-tracks area
|
||||
assert not any(t.contains(p) and not t.bounding_box().is_empty()
|
||||
for t in pour.tiles for p in pts[1:-1])
|
||||
|
||||
|
||||
def test_a_pour_other_nets_can_cross_is_not_trusted_to_connect():
|
||||
# the same zone with no no-tracks area over it: its fill could be split by
|
||||
# other nets' tracks, so its pads are joined by routing, not assumed
|
||||
places = "(place P1 5500 -7000 front 0) (place P2 8500 -7000 front 0)"
|
||||
dsn = parse_dsn(_dsn(f"(component P {places})", "(net N (pins P1-1 P2-1))", _ZONE))
|
||||
result, _, _ = route_dsn_board_exact(dsn)
|
||||
net = next(iter(result.routed_net_numbers))
|
||||
assert result.result.wires.get(net) # P1 and P2 got a track between them
|
||||
|
||||
Reference in New Issue
Block a user