Add shared orthogonal shove primitives module
Factor the exact router's shove geometry into route/shove.py: the conflicting-segment finder and displacement ladder are re-exported from exact_router (kept byte-unchanged so its shove count and geometry stay identical), and a room-oriented trace placement helper is added that returns the inserted tile owner ids for a journaled rollback.
This commit is contained in:
parent
faf2df42bc
commit
e67a11c833
124
src/freeroute/route/shove.py
Normal file
124
src/freeroute/route/shove.py
Normal file
@ -0,0 +1,124 @@
|
||||
"""Shared orthogonal shove primitives for the exact and room routers.
|
||||
|
||||
The exact-geometry router (:mod:`freeroute.route.exact_router`) grew the first
|
||||
shove implementation; the continuous room router reuses its geometry so both
|
||||
tracks agree on how a blocking trace's conflicting segment is found, how the
|
||||
perpendicular displacement ladder is generated, and how a reshaped trace is
|
||||
verified before it is committed.
|
||||
|
||||
To keep the exact track byte-for-byte identical (its tests assert an exact shove
|
||||
count and geometry), the two low-level geometry helpers still live in
|
||||
``exact_router`` and are re-exported here rather than moved. This module adds the
|
||||
room-specific placement helper, which returns the inserted tile owner ids: the
|
||||
room engine mutates already-committed nets, so it needs those ids for a real
|
||||
journaled rollback (the exact track's rollback is a no-op because it only ever
|
||||
verifies a shoved trace in isolation).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Iterator
|
||||
|
||||
from freeroute.board.search_tree import ShapeSearchTree, TreeShape
|
||||
from freeroute.geometry import IntBox, IntPoint, Polyline, PolylineShape
|
||||
|
||||
from .exact_router import _conflicting_segment as conflicting_segment
|
||||
|
||||
__all__ = [
|
||||
"conflicting_segment",
|
||||
"trace_tiles",
|
||||
"shove_displacements",
|
||||
"place_trace_owners",
|
||||
"force_place_owners",
|
||||
]
|
||||
|
||||
|
||||
def trace_tiles(corners: list[IntPoint], half_width: int) -> list[IntBox]:
|
||||
"""The copper tiles of the trace centreline ``corners`` at ``half_width``."""
|
||||
return PolylineShape(Polyline(corners), half_width).tiles()
|
||||
|
||||
|
||||
def shove_displacements(
|
||||
corners: list[IntPoint],
|
||||
seg_index: int,
|
||||
horizontal: bool,
|
||||
d_corners: list[IntPoint],
|
||||
half_width: int,
|
||||
clearance: int,
|
||||
) -> list[tuple[int, int]]:
|
||||
"""The perpendicular displacement ladder for shoving segment ``seg_index``.
|
||||
|
||||
Mirrors the exact router's ladder: displace the conflicting segment away
|
||||
from the crossing net at ``d_corners`` (its straight copper) by just enough
|
||||
to clear it (``2*half_width + clearance`` centre-to-centre), then step out in
|
||||
``half_width + clearance`` increments. The "away" side is tried first (past
|
||||
the far edge of the crossing net), then the near side, so the search is
|
||||
deterministic and integer-only.
|
||||
"""
|
||||
d0, d1 = d_corners[0], d_corners[1]
|
||||
margin = 2 * half_width + clearance
|
||||
fine = half_width + clearance
|
||||
ref = corners[seg_index]
|
||||
out: list[tuple[int, int]] = []
|
||||
if horizontal: # displace in y to clear the crossing net's y-span
|
||||
lo, hi = min(d0.y, d1.y), max(d0.y, d1.y)
|
||||
for extra in range(6):
|
||||
out.append((0, (hi + margin) - ref.y + extra * fine))
|
||||
out.append((0, (lo - margin) - ref.y - extra * fine))
|
||||
else: # displace in x
|
||||
lo, hi = min(d0.x, d1.x), max(d0.x, d1.x)
|
||||
for extra in range(6):
|
||||
out.append(((hi + margin) - ref.x + extra * fine, 0))
|
||||
out.append(((lo - margin) - ref.x - extra * fine, 0))
|
||||
return out
|
||||
|
||||
|
||||
def place_trace_owners(
|
||||
net_no: int,
|
||||
layer: int,
|
||||
corners: list[IntPoint],
|
||||
tree: ShapeSearchTree,
|
||||
owner: Iterator[int],
|
||||
half_width: int,
|
||||
clearance: int,
|
||||
outline: IntBox,
|
||||
) -> list[int] | None:
|
||||
"""Verify a trace's copper and insert it, returning the inserted owner ids.
|
||||
|
||||
The trace is accepted only if every tile stays inside ``outline`` and clears
|
||||
every other net exactly (via ``tree.clearance_conflict``). On rejection
|
||||
nothing is inserted and ``None`` is returned.
|
||||
"""
|
||||
boxes = PolylineShape(Polyline(corners), half_width).tiles()
|
||||
for box in boxes:
|
||||
if not outline.contains_box(box):
|
||||
return None
|
||||
if tree.clearance_conflict(box, net_no, layer, clearance):
|
||||
return None
|
||||
owners: list[int] = []
|
||||
for box in boxes:
|
||||
oid = next(owner)
|
||||
tree.insert(TreeShape(oid, net_no, frozenset({layer}), box, routed=True))
|
||||
owners.append(oid)
|
||||
return owners
|
||||
|
||||
|
||||
def force_place_owners(
|
||||
net_no: int,
|
||||
layer: int,
|
||||
corners: list[IntPoint],
|
||||
tree: ShapeSearchTree,
|
||||
owner: Iterator[int],
|
||||
half_width: int,
|
||||
) -> list[int]:
|
||||
"""Insert a trace's copper without any check, returning the owner ids.
|
||||
|
||||
Used only to restore an already-verified original trace during rollback: its
|
||||
tiles were clean before the attempt, so no re-verification is needed.
|
||||
"""
|
||||
owners: list[int] = []
|
||||
for box in PolylineShape(Polyline(corners), half_width).tiles():
|
||||
oid = next(owner)
|
||||
tree.insert(TreeShape(oid, net_no, frozenset({layer}), box, routed=True))
|
||||
owners.append(oid)
|
||||
return owners
|
||||
Loading…
x
Reference in New Issue
Block a user