Add gate-optimal placement and shove-in-rooms to the room router
Both upgrades sit behind an opt-in shove=False flag, so the default code path is byte-for-byte the foundation router (existing room tests are unchanged by construction). With shove=True: - Gate placement becomes occupancy-aware: each edge gate is projected into the largest other-net-free sub-span of the door, with the reserved intervals read straight from the search tree. With margin = clearance + half_width the room decomposition already keeps doors clearance-clear, so this reduces exactly to the old midpoint clamp where no other-net copper crosses the door. - When a net would be dropped, a transactional shove-in-rooms recovery nudges the committed trace(s) blocking it aside with the exact shove primitive and re-verifies against the clearance oracle, so both nets fit through one channel. Every moved trace is journaled and rolled back to its exact original tiles and wires on any failure -- the room engine mutates already-committed nets, so the rollback is real rather than the exact track's no-op. The board-wide has_violation(clearance) gate stays the final assertion. Thread the flag through pipeline.route_dsn_board_rooms / build_rooms_routing_result / route(engine='room', shove=...).
This commit is contained in:
parent
e67a11c833
commit
4042f80b81
@ -141,23 +141,26 @@ def build_exact_routing_result(
|
||||
|
||||
|
||||
def route_dsn_board_rooms(
|
||||
dsn: DsnBoard, *, layers: list[int] | None = None
|
||||
dsn: DsnBoard, *, layers: list[int] | None = None, shove: bool = False
|
||||
) -> tuple[ExactRouteResult, int, list[str]]:
|
||||
"""Route a parsed :class:`DsnBoard` with the continuous expansion-room track;
|
||||
return the result, the scale, and layer names."""
|
||||
return the result, the scale, and layer names. ``shove`` enables occupancy-
|
||||
aware gate placement and shove-in-rooms recovery of otherwise-dropped nets."""
|
||||
board = build_board(dsn)
|
||||
scale = max(dsn.resolution.value, 1)
|
||||
width_board = round(_rule_width_dsn(dsn) * scale)
|
||||
clearance_board = round(_rule_clearance_dsn(dsn) * scale)
|
||||
rooms = route_board_rooms(
|
||||
board, trace_width=width_board, clearance=clearance_board, layers=layers
|
||||
board, trace_width=width_board, clearance=clearance_board, layers=layers, shove=shove
|
||||
)
|
||||
return rooms, scale, [layer.name for layer in dsn.layers]
|
||||
|
||||
|
||||
def build_rooms_routing_result(dsn: DsnBoard, *, layers: list[int] | None = None) -> RoutingResult:
|
||||
def build_rooms_routing_result(
|
||||
dsn: DsnBoard, *, layers: list[int] | None = None, shove: bool = False
|
||||
) -> RoutingResult:
|
||||
"""Route ``dsn`` (room track) and convert to a DSN-unit RoutingResult."""
|
||||
rooms, scale, layer_names = route_dsn_board_rooms(dsn, layers=layers)
|
||||
rooms, scale, layer_names = route_dsn_board_rooms(dsn, layers=layers, shove=shove)
|
||||
return _to_routing_result(rooms.result, dsn, scale, layer_names)
|
||||
|
||||
|
||||
@ -171,13 +174,14 @@ def route(
|
||||
``"exact"`` (orthogonal, exact-geometry, DRC-verified — cleaner output where
|
||||
it succeeds), or ``"room"`` (continuous expansion-room — exact free-space
|
||||
decomposition, routes off-grid channels the grid/exact tracks cannot).
|
||||
``shove`` (exact engine only) tries moving existing traces aside.
|
||||
``shove`` (exact and room engines) tries moving existing traces aside; on the
|
||||
room engine it also turns on occupancy-aware gate placement.
|
||||
"""
|
||||
dsn = parse_dsn(dsn_text)
|
||||
if engine == "exact":
|
||||
result = build_exact_routing_result(dsn, layers=layers, shove=shove)
|
||||
elif engine == "room":
|
||||
result = build_rooms_routing_result(dsn, layers=layers)
|
||||
result = build_rooms_routing_result(dsn, layers=layers, shove=shove)
|
||||
else:
|
||||
result = build_routing_result(dsn, layers=layers)
|
||||
return write_ses(dsn, result)
|
||||
|
||||
@ -33,14 +33,51 @@ import heapq
|
||||
from freeroute.board.board import BasicBoard
|
||||
from freeroute.board.items import Pin
|
||||
from freeroute.board.search_tree import ShapeSearchTree, TreeShape
|
||||
from freeroute.geometry import IntBox, IntPoint, Polyline, PolylineShape
|
||||
from freeroute.geometry import IntBox, IntPoint, Polyline, PolylineShape, shove_segment
|
||||
|
||||
from .exact_router import ExactRouteResult, _routable_nets
|
||||
from .grid_router import RouteResult
|
||||
from .shove import (
|
||||
conflicting_segment,
|
||||
force_place_owners,
|
||||
place_trace_owners,
|
||||
shove_displacements,
|
||||
trace_tiles,
|
||||
)
|
||||
|
||||
__all__ = ["route_board_rooms"]
|
||||
|
||||
|
||||
@dataclass
|
||||
class _Accepted:
|
||||
"""A committed net's per-wire tile owners, for occupancy-aware placement and
|
||||
the journaled shove rollback.
|
||||
|
||||
``wires`` / ``vias`` are the *same* list objects as ``RouteResult.wires`` /
|
||||
``.vias`` for this net, so rewriting a shoved wire in place also updates the
|
||||
routed output. ``wire_owners`` is parallel to ``wires``: each entry is the
|
||||
list of :class:`~freeroute.board.search_tree.TreeShape` owner ids for that
|
||||
wire's copper tiles, so a later shove can locate, remove, and roll back a
|
||||
specific wire without disturbing a multi-wire net's other connections.
|
||||
"""
|
||||
|
||||
wires: list[tuple[int, list[IntPoint]]]
|
||||
vias: list[IntPoint]
|
||||
wire_owners: list[list[int]] = field(default_factory=list)
|
||||
via_owners: list[int] = field(default_factory=list)
|
||||
shove_count: int = 0
|
||||
|
||||
|
||||
@dataclass
|
||||
class _ShoveEntry:
|
||||
"""One moved blocker wire, enough to restore it exactly on rollback."""
|
||||
|
||||
blocker_net: int
|
||||
acc: _Accepted
|
||||
wire_index: int
|
||||
old_wire: tuple[int, list[IntPoint]]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class _Room:
|
||||
"""A convex free-space rectangle on one layer."""
|
||||
@ -245,11 +282,17 @@ def _dist(a: IntPoint, b: IntPoint) -> float:
|
||||
# --- path realization --------------------------------------------------------
|
||||
|
||||
|
||||
def _realize(graph, seq, start: IntPoint, goal: IntPoint, half_width):
|
||||
def _realize(graph, seq, start: IntPoint, goal: IntPoint, half_width, occupancy=None):
|
||||
"""Turn a room sequence into per-layer orthogonal wire segments + vias.
|
||||
|
||||
Routes through each door's mid-gate with an L-connector, splitting the path
|
||||
at via doors. Returns ``(wires, vias)`` in board units or ``None``.
|
||||
Routes through each door's gate with an L-connector, splitting the path at
|
||||
via doors. Returns ``(wires, vias)`` in board units or ``None``.
|
||||
|
||||
``occupancy`` (``None`` by default) enables occupancy-aware gate placement:
|
||||
when it is ``(tree, net_no, clearance)`` each edge gate is projected into the
|
||||
largest other-net-free sub-span of the door instead of the raw midpoint. With
|
||||
no other-net routed copper on the door this reduces exactly to the midpoint
|
||||
clamp, so the ``occupancy is None`` path stays byte-for-byte the old router.
|
||||
"""
|
||||
wires: list[tuple[int, list[IntPoint]]] = []
|
||||
vias: list[IntPoint] = []
|
||||
@ -261,7 +304,11 @@ def _realize(graph, seq, start: IntPoint, goal: IntPoint, half_width):
|
||||
if door is None:
|
||||
return None
|
||||
if door.kind == "edge":
|
||||
if occupancy is None:
|
||||
gate = _edge_gate(door, pts[-1], half_width)
|
||||
else:
|
||||
tree, net_no, clearance = occupancy
|
||||
gate = _project_gate(door, pts[-1], half_width, clearance, tree, net_no, layer)
|
||||
pts.append(gate)
|
||||
else: # via: close the current-layer wire, drop a via, start next layer
|
||||
gate = _via_gate(door, pts[-1], half_width)
|
||||
@ -296,6 +343,88 @@ def _via_gate(door: _Door, from_pt: IntPoint, half_width: int) -> IntPoint:
|
||||
return IntPoint(x, y)
|
||||
|
||||
|
||||
def _project_gate(
|
||||
door: _Door, from_pt: IntPoint, half_width: int, clearance: int, tree, net_no: int, layer: int
|
||||
) -> IntPoint:
|
||||
"""An occupancy-aware gate point on an edge door.
|
||||
|
||||
Ports ``LocateFoundConnectionAlgo``'s projection of the current from-point
|
||||
onto the door shrunk by half the trace width, but subtracts the intervals
|
||||
already reserved by other-net routed copper crossing the door (the
|
||||
``ExpansionDoor`` section reservations, read straight from the search tree).
|
||||
The trace is seated in the largest remaining free sub-span, at the point
|
||||
nearest the ideal projection (tie-break: lowest coordinate). With no other-net
|
||||
routed tile on the door this returns exactly :func:`_edge_gate`'s clamp.
|
||||
"""
|
||||
if door.lo.x == door.hi.x: # vertical edge -> choose the y coordinate
|
||||
axis_lo, axis_hi, vertical = door.lo.y, door.hi.y, True
|
||||
ideal = from_pt.y
|
||||
else: # horizontal edge -> choose the x coordinate
|
||||
axis_lo, axis_hi, vertical = door.lo.x, door.hi.x, False
|
||||
ideal = from_pt.x
|
||||
lo = axis_lo + half_width
|
||||
hi = axis_hi - half_width
|
||||
if lo > hi: # door too narrow for the trace: same midpoint fallback as _clamp
|
||||
value = (axis_lo + axis_hi) // 2
|
||||
else:
|
||||
ideal = max(lo, min(hi, ideal))
|
||||
forbidden = _door_forbidden_intervals(
|
||||
door, half_width, clearance, tree, net_no, layer, vertical
|
||||
)
|
||||
value = _best_free_point(lo, hi, forbidden, ideal)
|
||||
return IntPoint(door.lo.x, value) if vertical else IntPoint(value, door.lo.y)
|
||||
|
||||
|
||||
def _door_forbidden_intervals(door, half_width, clearance, tree, net_no, layer, vertical):
|
||||
"""Axis intervals a trace centre must avoid: one per other-net routed tile
|
||||
crossing the door edge, widened by ``clearance + half_width`` on each side."""
|
||||
edge_box = IntBox(door.lo.x, door.lo.y, door.hi.x, door.hi.y)
|
||||
query = edge_box.offset(clearance + half_width)
|
||||
reach = clearance + half_width
|
||||
forbidden: list[tuple[int, int]] = []
|
||||
for shape in tree.overlapping(query):
|
||||
if not shape.routed or shape.net_no == net_no or layer not in shape.layers:
|
||||
continue
|
||||
tile = shape.tile
|
||||
if vertical:
|
||||
forbidden.append((tile.ll.y - reach, tile.ur.y + reach))
|
||||
else:
|
||||
forbidden.append((tile.ll.x - reach, tile.ur.x + reach))
|
||||
return forbidden
|
||||
|
||||
|
||||
def _best_free_point(lo: int, hi: int, forbidden, ideal: int) -> int:
|
||||
"""The point in the largest free sub-interval of ``[lo, hi]`` nearest ``ideal``.
|
||||
|
||||
Free sub-intervals are ``[lo, hi]`` minus the (open) ``forbidden`` intervals;
|
||||
the largest is chosen (tie-break: lowest coordinate) so a trace leaves the
|
||||
most room for the next one, then clamped toward ``ideal`` inside it.
|
||||
"""
|
||||
free = _free_intervals(lo, hi, forbidden)
|
||||
if not free:
|
||||
return max(lo, min(hi, ideal))
|
||||
a, b = max(free, key=lambda iv: (iv[1] - iv[0], -iv[0]))
|
||||
return max(a, min(b, ideal))
|
||||
|
||||
|
||||
def _free_intervals(lo: int, hi: int, forbidden) -> list[tuple[int, int]]:
|
||||
"""Closed integer sub-intervals of ``[lo, hi]`` outside every open forbidden
|
||||
interval. Interval boundaries stay free: a centre exactly ``clearance`` away
|
||||
from a tile is allowed by the exact clearance rule."""
|
||||
free: list[tuple[int, int]] = []
|
||||
cur = lo
|
||||
for f_lo, f_hi in sorted(forbidden):
|
||||
seg_hi = min(f_lo, hi)
|
||||
if cur <= hi and seg_hi >= cur:
|
||||
free.append((cur, seg_hi))
|
||||
cur = max(cur, min(f_hi, hi))
|
||||
if cur >= hi:
|
||||
break
|
||||
if cur <= hi:
|
||||
free.append((cur, hi))
|
||||
return free
|
||||
|
||||
|
||||
def _orthogonalize(points: list[IntPoint]) -> list[IntPoint]:
|
||||
"""Connect gate points with axis-aligned segments (L-shaped between each)."""
|
||||
out = [points[0]]
|
||||
@ -338,8 +467,21 @@ def route_board_rooms(
|
||||
clearance: int,
|
||||
layers: list[int] | None = None,
|
||||
via_cost: float = 50000.0,
|
||||
shove: bool = False,
|
||||
max_shove_depth: int = 5,
|
||||
shove_cap: int = 3,
|
||||
) -> ExactRouteResult:
|
||||
"""Route ``board`` with the continuous expansion-room track (foundation)."""
|
||||
"""Route ``board`` with the continuous expansion-room track.
|
||||
|
||||
``shove`` (opt-in) turns on two continuous, sub-cell upgrades that the grid
|
||||
could not do: occupancy-aware gate placement (seat each trace at the best
|
||||
point in a door, not its midpoint) and shove-in-rooms (when a net would be
|
||||
dropped, nudge the blocking committed trace(s) aside with the exact shove
|
||||
primitive and re-route, rolling every moved trace back on any failure). With
|
||||
``shove=False`` every new branch is skipped and the output is byte-for-byte
|
||||
the foundation router. ``max_shove_depth`` bounds the distinct blocker nets
|
||||
per recovery; ``shove_cap`` bounds how often one trace may be moved.
|
||||
"""
|
||||
signal_layers = layers or [
|
||||
i for i, layer in enumerate(board.layer_structure.arr) if layer.is_signal
|
||||
]
|
||||
@ -369,10 +511,12 @@ def route_board_rooms(
|
||||
|
||||
result = RouteResult(half_width)
|
||||
via_layers = frozenset(signal_layers)
|
||||
accepted: dict[int, _Accepted] = {}
|
||||
shoves = 0
|
||||
for net_no in sorted(_routable_nets(board, signal_layers)):
|
||||
pins = _net_pins(board, net_no, signal_layers)
|
||||
for a, b in zip(pins, pins[1:], strict=False):
|
||||
_route_connection(
|
||||
shoves += _route_connection(
|
||||
net_no,
|
||||
a,
|
||||
b,
|
||||
@ -387,10 +531,14 @@ def route_board_rooms(
|
||||
trace_width,
|
||||
via_cost,
|
||||
via_layers,
|
||||
shove,
|
||||
accepted,
|
||||
max_shove_depth,
|
||||
shove_cap,
|
||||
)
|
||||
|
||||
drc_clean = tree.has_violation(clearance) is None
|
||||
return ExactRouteResult(result=result, tree=tree, drc_clean=drc_clean)
|
||||
return ExactRouteResult(result=result, tree=tree, drc_clean=drc_clean, shoves=shoves)
|
||||
|
||||
|
||||
def _route_connection(
|
||||
@ -408,6 +556,10 @@ def _route_connection(
|
||||
trace_width,
|
||||
via_cost,
|
||||
via_layers,
|
||||
shove=False,
|
||||
accepted=None,
|
||||
max_shove_depth=5,
|
||||
shove_cap=3,
|
||||
):
|
||||
rooms_by_layer = {
|
||||
layer: _free_rooms(tree, outline, layer, net_no, margin, half_width)
|
||||
@ -418,41 +570,247 @@ def _route_connection(
|
||||
b_layers = [layer for layer in b.layers if layer in signal_layers]
|
||||
starts = _rooms_containing(graph, a.location, a_layers)
|
||||
goals = _rooms_containing(graph, b.location, b_layers)
|
||||
if not starts or not goals:
|
||||
return
|
||||
seq = _search_rooms(graph, starts, set(goals), b.location, via_cost)
|
||||
if seq is None:
|
||||
return
|
||||
realized = _realize(graph, seq, a.location, b.location, half_width)
|
||||
if realized is None:
|
||||
return
|
||||
wires, vias = realized
|
||||
|
||||
# verify every piece is exactly DRC-clean before committing
|
||||
realized = None
|
||||
if starts and goals:
|
||||
seq = _search_rooms(graph, starts, set(goals), b.location, via_cost)
|
||||
if seq is not None:
|
||||
occupancy = (tree, net_no, clearance) if shove else None
|
||||
realized = _realize(graph, seq, a.location, b.location, half_width, occupancy)
|
||||
|
||||
if realized is not None:
|
||||
wires, vias = realized
|
||||
boxes = _collect_boxes(wires, vias, half_width, via_layers, outline)
|
||||
if boxes is not None and not any(
|
||||
tree.clearance_conflict(box, net_no, layer, clearance)
|
||||
for box, box_layers in boxes
|
||||
for layer in box_layers
|
||||
):
|
||||
_commit(net_no, wires, vias, tree, result, owner, half_width, via_layers, shove, accepted)
|
||||
return 0
|
||||
|
||||
# the net would be dropped: try a transactional shove-in-rooms recovery
|
||||
if shove:
|
||||
return _shove_recover_room(
|
||||
net_no,
|
||||
a,
|
||||
b,
|
||||
tree,
|
||||
result,
|
||||
accepted,
|
||||
owner,
|
||||
outline,
|
||||
signal_layers,
|
||||
half_width,
|
||||
clearance,
|
||||
max_shove_depth,
|
||||
shove_cap,
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
def _collect_boxes(wires, vias, half_width, via_layers, outline):
|
||||
"""The ``(box, layers)`` copper tiles of a realized route, or ``None`` if any
|
||||
tile leaves the board outline (the same early-out as the old inline check)."""
|
||||
boxes: list[tuple[IntBox, frozenset[int]]] = []
|
||||
for layer, corners in wires:
|
||||
for box in PolylineShape(Polyline(corners), half_width).tiles():
|
||||
if not outline.contains_box(box):
|
||||
return
|
||||
return None
|
||||
boxes.append((box, frozenset({layer})))
|
||||
for loc in vias:
|
||||
vbox = IntBox(
|
||||
loc.x - half_width, loc.y - half_width, loc.x + half_width, loc.y + half_width
|
||||
)
|
||||
boxes.append((vbox, via_layers))
|
||||
if any(
|
||||
tree.clearance_conflict(box, net_no, layer, clearance)
|
||||
for box, box_layers in boxes
|
||||
for layer in box_layers
|
||||
):
|
||||
return
|
||||
return boxes
|
||||
|
||||
for box, box_layers in boxes:
|
||||
tree.insert(TreeShape(next(owner), net_no, box_layers, box, routed=True))
|
||||
|
||||
def _commit(net_no, wires, vias, tree, result, owner, half_width, via_layers, shove, accepted):
|
||||
"""Insert a verified route's copper and record it in the routed output.
|
||||
|
||||
With ``shove=False`` this is the foundation router's exact commit sequence.
|
||||
With ``shove=True`` the tiles are additionally grouped per wire into the
|
||||
``accepted`` registry so a later recovery can move or roll back one wire.
|
||||
"""
|
||||
if not shove:
|
||||
for layer, corners in wires:
|
||||
for box in PolylineShape(Polyline(corners), half_width).tiles():
|
||||
tree.insert(TreeShape(next(owner), net_no, frozenset({layer}), box, routed=True))
|
||||
for loc in vias:
|
||||
vbox = IntBox(
|
||||
loc.x - half_width, loc.y - half_width, loc.x + half_width, loc.y + half_width
|
||||
)
|
||||
tree.insert(TreeShape(next(owner), net_no, via_layers, vbox, routed=True))
|
||||
for layer, corners in wires:
|
||||
result.add_wire(net_no, layer, corners)
|
||||
for loc in vias:
|
||||
result.add_via(net_no, loc)
|
||||
return
|
||||
|
||||
acc = accepted.get(net_no)
|
||||
if acc is None:
|
||||
acc = _Accepted(
|
||||
wires=result.wires.setdefault(net_no, []), vias=result.vias.setdefault(net_no, [])
|
||||
)
|
||||
accepted[net_no] = acc
|
||||
for layer, corners in wires:
|
||||
w_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))
|
||||
w_owners.append(oid)
|
||||
result.add_wire(net_no, layer, corners)
|
||||
acc.wire_owners.append(w_owners)
|
||||
for loc in vias:
|
||||
vbox = IntBox(
|
||||
loc.x - half_width, loc.y - half_width, loc.x + half_width, loc.y + half_width
|
||||
)
|
||||
oid = next(owner)
|
||||
tree.insert(TreeShape(oid, net_no, via_layers, vbox, routed=True))
|
||||
result.add_via(net_no, loc)
|
||||
acc.via_owners.append(oid)
|
||||
|
||||
|
||||
# --- shove-in-rooms recovery -------------------------------------------------
|
||||
|
||||
|
||||
def _shove_recover_room(
|
||||
net_no,
|
||||
a,
|
||||
b,
|
||||
tree,
|
||||
result,
|
||||
accepted,
|
||||
owner,
|
||||
outline,
|
||||
signal_layers,
|
||||
half_width,
|
||||
clearance,
|
||||
max_shove_depth,
|
||||
shove_cap,
|
||||
) -> int:
|
||||
"""Recover a dropped net by nudging the committed trace(s) that block it.
|
||||
|
||||
MVP (mirrors the exact track): a straight, axis-aligned 2-pin net on one
|
||||
signal layer. Its straight copper is the corridor; every routed different-net
|
||||
trace crossing it is shoved perpendicular with the shared exact primitive and
|
||||
re-verified against the tree, then the net is placed straight. A static tile
|
||||
(pad/keepout) in the corridor, or a blocker that will not move cleanly, aborts
|
||||
the whole attempt and rolls every moved trace back to its exact original
|
||||
tiles and wires. Returns the number of successful moves (0 = still dropped).
|
||||
"""
|
||||
if a.location.x != b.location.x and a.location.y != b.location.y:
|
||||
return 0 # only straight nets in this MVP
|
||||
layer = next(iter(set(a.layers) & set(b.layers) & set(signal_layers)), None)
|
||||
if layer is None:
|
||||
return 0
|
||||
corners = [a.location, b.location]
|
||||
d_boxes = trace_tiles(corners, half_width)
|
||||
|
||||
blockers: dict[int, set[int]] = {}
|
||||
for box in d_boxes:
|
||||
expanded = box.offset(clearance)
|
||||
for shape in tree.overlapping(expanded):
|
||||
if shape.net_no == net_no or layer not in shape.layers:
|
||||
continue
|
||||
if not shape.tile.overlaps(expanded):
|
||||
continue
|
||||
if not shape.routed:
|
||||
return 0 # a pad or keepout sits in the corridor: cannot shove it
|
||||
blockers.setdefault(shape.net_no, set()).add(shape.owner)
|
||||
if not blockers or len(blockers) > max_shove_depth:
|
||||
return 0
|
||||
|
||||
journal: list[_ShoveEntry] = []
|
||||
for blocker_net in sorted(blockers):
|
||||
acc = accepted.get(blocker_net) if accepted is not None else None
|
||||
if acc is None or acc.shove_count >= shove_cap:
|
||||
_rollback_room(journal, tree, owner, half_width)
|
||||
return 0
|
||||
if not _shove_blocker(
|
||||
blocker_net, acc, corners, layer, tree, owner, half_width, clearance, outline, journal
|
||||
):
|
||||
_rollback_room(journal, tree, owner, half_width)
|
||||
return 0
|
||||
|
||||
# the corridor must now be clean and inside the outline before we commit it
|
||||
if any(not outline.contains_box(box) for box in d_boxes) or any(
|
||||
tree.clearance_conflict(box, net_no, layer, clearance) for box in d_boxes
|
||||
):
|
||||
_rollback_room(journal, tree, owner, half_width)
|
||||
return 0
|
||||
|
||||
owners = force_place_owners(net_no, layer, corners, tree, owner, half_width)
|
||||
acc = _Accepted(
|
||||
wires=result.wires.setdefault(net_no, []), vias=result.vias.setdefault(net_no, [])
|
||||
)
|
||||
result.add_wire(net_no, layer, corners)
|
||||
acc.wire_owners.append(owners)
|
||||
accepted[net_no] = acc
|
||||
return len(journal) + 1
|
||||
|
||||
|
||||
def _shove_blocker(
|
||||
blocker_net, acc, d_corners, layer, tree, owner, half_width, clearance, outline, journal
|
||||
) -> bool:
|
||||
"""Move ``blocker_net``'s conflicting wire clear of the corridor ``d_corners``.
|
||||
|
||||
Finds the single wire of the blocker whose axis-aligned run crosses the
|
||||
corridor, removes its tiles, and walks the perpendicular displacement ladder
|
||||
until the reshaped wire places cleanly (outline + exact clearance). On success
|
||||
the move is journaled for rollback; on failure the wire's original tiles are
|
||||
restored and ``False`` is returned.
|
||||
"""
|
||||
d_boxes = trace_tiles(d_corners, half_width)
|
||||
for wire_index, (seg_layer, corners) in enumerate(acc.wires):
|
||||
if seg_layer != layer:
|
||||
continue
|
||||
seg_index, horizontal = conflicting_segment(corners, d_boxes, half_width, clearance)
|
||||
if seg_index is None:
|
||||
continue
|
||||
|
||||
old_owners = list(acc.wire_owners[wire_index])
|
||||
old_wire = (seg_layer, list(corners))
|
||||
for oid in old_owners:
|
||||
tree.remove_owner(oid)
|
||||
|
||||
for dx, dy in shove_displacements(
|
||||
corners, seg_index, horizontal, d_corners, half_width, clearance
|
||||
):
|
||||
if dx == 0 and dy == 0:
|
||||
continue
|
||||
new_corners = shove_segment(list(corners), seg_index, dx, dy)
|
||||
new_owners = place_trace_owners(
|
||||
blocker_net, seg_layer, new_corners, tree, owner, half_width, clearance, outline
|
||||
)
|
||||
if new_owners is not None:
|
||||
journal.append(_ShoveEntry(blocker_net, acc, wire_index, old_wire))
|
||||
acc.wires[wire_index] = (seg_layer, new_corners)
|
||||
acc.wire_owners[wire_index] = new_owners
|
||||
acc.shove_count += 1
|
||||
return True
|
||||
|
||||
# no clean displacement: restore the wire's original tiles and give up
|
||||
acc.wire_owners[wire_index] = force_place_owners(
|
||||
blocker_net, seg_layer, list(corners), tree, owner, half_width
|
||||
)
|
||||
return False
|
||||
return False
|
||||
|
||||
|
||||
def _rollback_room(journal, tree, owner, half_width) -> int:
|
||||
"""Undo every journaled shove, restoring exact original tiles and wires."""
|
||||
for entry in reversed(journal):
|
||||
for oid in entry.acc.wire_owners[entry.wire_index]:
|
||||
tree.remove_owner(oid)
|
||||
layer, old_corners = entry.old_wire
|
||||
entry.acc.wire_owners[entry.wire_index] = force_place_owners(
|
||||
entry.blocker_net, layer, old_corners, tree, owner, half_width
|
||||
)
|
||||
entry.acc.wires[entry.wire_index] = (layer, old_corners)
|
||||
entry.acc.shove_count -= 1
|
||||
return 0
|
||||
|
||||
|
||||
def _net_pins(board: BasicBoard, net_no: int, signal_layers) -> list[Pin]:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user