diff --git a/src/freeroute/route/grid_router.py b/src/freeroute/route/grid_router.py index 905e693..7361a6d 100644 --- a/src/freeroute/route/grid_router.py +++ b/src/freeroute/route/grid_router.py @@ -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) diff --git a/tests/route/test_real_board_geometry.py b/tests/route/test_real_board_geometry.py index 43e56fa..7ce7b55 100644 --- a/tests/route/test_real_board_geometry.py +++ b/tests/route/test_real_board_geometry.py @@ -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