Add continuous expansion-room routing track
A third engine (engine='room') that replaces the fixed routing grid with FreeRouting's free-space rooms. Per layer, the space not occupied by items (inflated by the routing clearance) is decomposed into exact rectangles via coordinate compression (the orthogonal analogue of CompleteFreeSpaceExpansionRoom); adjacent rooms share an edge door, and a via door joins overlapping rooms on two layers. The maze search is an A* over rooms-through-doors (cost = geometric distance + via cost), so a path may cross a door anywhere along its width rather than at a quantized cell. The found room sequence is realized into an exact orthogonal Polyline through the door mid-gates and verified DRC-clean against the ShapeSearchTree, dropping the net if it cannot be made exactly clean. Maps to autoroute/: ExpansionRoom / CompleteFreeSpaceExpansionRoom (rooms), ExpansionDoor / TargetItemExpansionDoor (doors), MazeSearchAlgo / AutorouteEngine (search), LocateFoundConnectionAlgo (realization). Foundation scope: rooms are axis-aligned rectangles (not general Simplex tiles); realization uses an L-connector through door mid-gates. Any-angle rooms, optimal gate placement (true sub-cell shove), and optimization passes are follow-ups. The grid and exact tracks are unchanged; this is opt-in. Routes simple/crossing (with a via) and all four multi-pin nets of the real KiCad board, DRC-clean.
This commit is contained in:
parent
e58f63e0f1
commit
6a24ffc540
@ -18,11 +18,14 @@ from .exact_router import ExactRouteResult, route_board_exact
|
||||
from .grid_router import GridRouter, RouteResult, route_board
|
||||
from .pipeline import (
|
||||
build_exact_routing_result,
|
||||
build_rooms_routing_result,
|
||||
build_routing_result,
|
||||
route,
|
||||
route_dsn_board,
|
||||
route_dsn_board_exact,
|
||||
route_dsn_board_rooms,
|
||||
)
|
||||
from .room_router import route_board_rooms
|
||||
|
||||
__all__ = [
|
||||
"GridRouter",
|
||||
@ -35,4 +38,7 @@ __all__ = [
|
||||
"route_board_exact",
|
||||
"build_exact_routing_result",
|
||||
"route_dsn_board_exact",
|
||||
"route_board_rooms",
|
||||
"build_rooms_routing_result",
|
||||
"route_dsn_board_rooms",
|
||||
]
|
||||
|
||||
@ -18,6 +18,7 @@ from freeroute.ses import RoutedVia, RoutedWire, RoutingResult, write_ses
|
||||
|
||||
from .exact_router import ExactRouteResult, route_board_exact
|
||||
from .grid_router import RouteResult, route_board
|
||||
from .room_router import route_board_rooms
|
||||
|
||||
__all__ = ["route", "route_dsn_board", "build_routing_result"]
|
||||
|
||||
@ -139,20 +140,44 @@ def build_exact_routing_result(
|
||||
return _to_routing_result(exact.result, dsn, scale, layer_names)
|
||||
|
||||
|
||||
def route_dsn_board_rooms(
|
||||
dsn: DsnBoard, *, layers: list[int] | None = None
|
||||
) -> tuple[ExactRouteResult, int, list[str]]:
|
||||
"""Route a parsed :class:`DsnBoard` with the continuous expansion-room track;
|
||||
return the result, the scale, and layer names."""
|
||||
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
|
||||
)
|
||||
return rooms, scale, [layer.name for layer in dsn.layers]
|
||||
|
||||
|
||||
def build_rooms_routing_result(dsn: DsnBoard, *, layers: list[int] | None = None) -> RoutingResult:
|
||||
"""Route ``dsn`` (room track) and convert to a DSN-unit RoutingResult."""
|
||||
rooms, scale, layer_names = route_dsn_board_rooms(dsn, layers=layers)
|
||||
return _to_routing_result(rooms.result, dsn, scale, layer_names)
|
||||
|
||||
|
||||
def route(
|
||||
dsn_text: str, *, layers: list[int] | None = None, engine: str = "grid", shove: bool = False
|
||||
) -> str:
|
||||
"""Route a Specctra DSN string and return the routed SES string.
|
||||
|
||||
``engine`` selects the routing track: ``"grid"`` (default; the multi-layer
|
||||
rip-up grid MVP — highest coverage, kept for backward compatibility) or
|
||||
rip-up grid MVP — highest coverage, kept for backward compatibility),
|
||||
``"exact"`` (orthogonal, exact-geometry, DRC-verified — cleaner output where
|
||||
it succeeds). ``shove`` (exact engine only) tries moving existing traces
|
||||
aside to recover dropped nets.
|
||||
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.
|
||||
"""
|
||||
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)
|
||||
else:
|
||||
result = build_routing_result(dsn, layers=layers)
|
||||
return write_ses(dsn, result)
|
||||
|
||||
470
src/freeroute/route/room_router.py
Normal file
470
src/freeroute/route/room_router.py
Normal file
@ -0,0 +1,470 @@
|
||||
"""Continuous expansion-room routing track (foundation).
|
||||
|
||||
The grid and exact tracks quantize space into fixed ``width + clearance`` cells.
|
||||
This track replaces that grid with FreeRouting's idea of **free-space rooms**:
|
||||
per layer, the space *not* occupied by items (inflated by the routing clearance)
|
||||
is decomposed into exact convex tiles (here axis-aligned :class:`IntBox` rooms
|
||||
via coordinate compression, the orthogonal analogue of
|
||||
``CompleteFreeSpaceExpansionRoom``). Adjacent rooms share a **door** on their
|
||||
common edge (and a **via door** joins the same footprint on two layers); the
|
||||
maze search is an A* over rooms-through-doors, so a path may cross a door at any
|
||||
point along its width — continuous, not cell-quantized. The found room sequence
|
||||
is realized into an exact orthogonal :class:`Polyline` and verified DRC-clean
|
||||
against the :class:`ShapeSearchTree`, exactly like the exact track.
|
||||
|
||||
**Foundation scope (this is large — the tail is deferred):** rooms are
|
||||
axis-aligned rectangles (not general ``Simplex`` tiles); path realization routes
|
||||
through door mid-gates with an L-connector and drops a net if the realized trace
|
||||
cannot be made exactly clean. Any-angle rooms, optimal gate placement (true
|
||||
sub-cell shove), and optimization passes are follow-ups. The grid and exact
|
||||
tracks are unchanged; this is opt-in via ``engine="room"``.
|
||||
|
||||
Maps to FreeRouting: rooms ← ``ExpansionRoom`` /
|
||||
``CompleteFreeSpaceExpansionRoom``; doors ← ``ExpansionDoor`` /
|
||||
``TargetItemExpansionDoor``; search ← ``MazeSearchAlgo`` / ``AutorouteEngine``;
|
||||
realization ← ``LocateFoundConnectionAlgo``.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
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 .exact_router import ExactRouteResult, _routable_nets
|
||||
from .grid_router import RouteResult
|
||||
|
||||
__all__ = ["route_board_rooms"]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class _Room:
|
||||
"""A convex free-space rectangle on one layer."""
|
||||
|
||||
id: int
|
||||
layer: int
|
||||
box: IntBox
|
||||
|
||||
def center(self) -> IntPoint:
|
||||
return IntPoint((self.box.ll.x + self.box.ur.x) // 2, (self.box.ll.y + self.box.ur.y) // 2)
|
||||
|
||||
|
||||
@dataclass
|
||||
class _Door:
|
||||
"""A passage between two rooms: a shared edge, or a via between layers."""
|
||||
|
||||
other: int # neighbouring room id
|
||||
kind: str # "edge" | "via"
|
||||
# gate geometry: for an edge door the shared segment, for a via the overlap box
|
||||
lo: IntPoint
|
||||
hi: IntPoint
|
||||
|
||||
|
||||
@dataclass
|
||||
class _Graph:
|
||||
rooms: dict[int, _Room] = field(default_factory=dict)
|
||||
doors: dict[int, list[_Door]] = field(default_factory=dict)
|
||||
by_layer: dict[int, list[int]] = field(default_factory=dict)
|
||||
|
||||
|
||||
# --- free-space decomposition ------------------------------------------------
|
||||
|
||||
|
||||
def _free_rooms(
|
||||
tree: ShapeSearchTree, outline: IntBox, layer: int, net_no: int, margin: int, half_width: int
|
||||
) -> list[IntBox]:
|
||||
"""Decompose the centreline-free space on ``layer`` into rectangles.
|
||||
|
||||
Obstacles are every other-net tile on this layer, inflated by ``margin``
|
||||
(clearance + half-width) so a centreline inside a room keeps the trace clear.
|
||||
The routing region is the outline shrunk by ``half_width``.
|
||||
"""
|
||||
region = outline.offset(-half_width)
|
||||
if region.is_empty():
|
||||
return []
|
||||
obstacles: list[IntBox] = []
|
||||
for shape in tree.all_shapes():
|
||||
if net_no in (shape.net_no,) or layer not in shape.layers:
|
||||
continue
|
||||
infl = shape.tile.offset(margin)
|
||||
clipped = infl.intersection(region)
|
||||
if not clipped.is_empty() and clipped.dimension() == 2:
|
||||
obstacles.append(clipped)
|
||||
|
||||
xs = sorted(
|
||||
{region.ll.x, region.ur.x} | {o.ll.x for o in obstacles} | {o.ur.x for o in obstacles}
|
||||
)
|
||||
ys = sorted(
|
||||
{region.ll.y, region.ur.y} | {o.ll.y for o in obstacles} | {o.ur.y for o in obstacles}
|
||||
)
|
||||
|
||||
rooms: list[IntBox] = []
|
||||
for j in range(len(ys) - 1):
|
||||
y0, y1 = ys[j], ys[j + 1]
|
||||
if y1 <= y0:
|
||||
continue
|
||||
run: IntBox | None = None
|
||||
for i in range(len(xs) - 1):
|
||||
x0, x1 = xs[i], xs[i + 1]
|
||||
if x1 <= x0:
|
||||
continue
|
||||
cx, cy = (x0 + x1) // 2, (y0 + y1) // 2
|
||||
blocked = any(o.contains_inside(IntPoint(cx, cy)) for o in obstacles)
|
||||
if blocked:
|
||||
if run is not None:
|
||||
rooms.append(run)
|
||||
run = None
|
||||
continue
|
||||
cell = IntBox(x0, y0, x1, y1)
|
||||
run = cell if run is None else run.union(cell) # merge along x
|
||||
if run is not None:
|
||||
rooms.append(run)
|
||||
return rooms
|
||||
|
||||
|
||||
# --- doors -------------------------------------------------------------------
|
||||
|
||||
|
||||
def _build_graph(rooms_by_layer: dict[int, list[IntBox]], trace_width: int) -> _Graph:
|
||||
graph = _Graph()
|
||||
next_id = 0
|
||||
layer_rooms: dict[int, list[_Room]] = {}
|
||||
for layer, boxes in rooms_by_layer.items():
|
||||
layer_rooms[layer] = []
|
||||
for box in boxes:
|
||||
room = _Room(next_id, layer, box)
|
||||
graph.rooms[room.id] = room
|
||||
graph.doors[room.id] = []
|
||||
graph.by_layer.setdefault(layer, []).append(room.id)
|
||||
layer_rooms[layer].append(room)
|
||||
next_id += 1
|
||||
|
||||
# same-layer edge doors (shared edge with overlap >= trace_width)
|
||||
for rooms in layer_rooms.values():
|
||||
for i in range(len(rooms)):
|
||||
a = rooms[i]
|
||||
for j in range(i + 1, len(rooms)):
|
||||
b = rooms[j]
|
||||
door = _edge_door(a.box, b.box, trace_width)
|
||||
if door is not None:
|
||||
lo, hi = door
|
||||
graph.doors[a.id].append(_Door(b.id, "edge", lo, hi))
|
||||
graph.doors[b.id].append(_Door(a.id, "edge", lo, hi))
|
||||
|
||||
# via doors: overlapping rooms on different layers
|
||||
layers = sorted(layer_rooms)
|
||||
for li in range(len(layers)):
|
||||
for lj in range(li + 1, len(layers)):
|
||||
for a in layer_rooms[layers[li]]:
|
||||
for b in layer_rooms[layers[lj]]:
|
||||
inter = a.box.intersection(b.box)
|
||||
if not inter.is_empty() and inter.min_width() >= trace_width:
|
||||
lo = IntPoint(inter.ll.x, inter.ll.y)
|
||||
hi = IntPoint(inter.ur.x, inter.ur.y)
|
||||
graph.doors[a.id].append(_Door(b.id, "via", lo, hi))
|
||||
graph.doors[b.id].append(_Door(a.id, "via", lo, hi))
|
||||
return graph
|
||||
|
||||
|
||||
def _edge_door(a: IntBox, b: IntBox, trace_width: int):
|
||||
"""Shared edge segment of two boxes if they abut with enough overlap."""
|
||||
if a.ur.x == b.ll.x or b.ur.x == a.ll.x: # vertical shared edge
|
||||
x = a.ur.x if a.ur.x == b.ll.x else a.ll.x
|
||||
lo_y = max(a.ll.y, b.ll.y)
|
||||
hi_y = min(a.ur.y, b.ur.y)
|
||||
if hi_y - lo_y >= trace_width:
|
||||
return IntPoint(x, lo_y), IntPoint(x, hi_y)
|
||||
if a.ur.y == b.ll.y or b.ur.y == a.ll.y: # horizontal shared edge
|
||||
y = a.ur.y if a.ur.y == b.ll.y else a.ll.y
|
||||
lo_x = max(a.ll.x, b.ll.x)
|
||||
hi_x = min(a.ur.x, b.ur.x)
|
||||
if hi_x - lo_x >= trace_width:
|
||||
return IntPoint(lo_x, y), IntPoint(hi_x, y)
|
||||
return None
|
||||
|
||||
|
||||
# --- room A* -----------------------------------------------------------------
|
||||
|
||||
|
||||
def _rooms_containing(graph: _Graph, point: IntPoint, layers) -> list[int]:
|
||||
return [
|
||||
rid
|
||||
for layer in layers
|
||||
for rid in graph.by_layer.get(layer, [])
|
||||
if graph.rooms[rid].box.contains(point)
|
||||
]
|
||||
|
||||
|
||||
def _search_rooms(graph: _Graph, starts, goals, goal_pt, via_cost):
|
||||
goal_set = set(goals)
|
||||
if not goal_set:
|
||||
return None
|
||||
open_heap: list[tuple[float, int]] = []
|
||||
g_score: dict[int, float] = {}
|
||||
came: dict[int, tuple[int, _Door]] = {}
|
||||
for s in sorted(starts):
|
||||
g_score[s] = 0.0
|
||||
heapq.heappush(open_heap, (_dist(graph.rooms[s].center(), goal_pt), s))
|
||||
while open_heap:
|
||||
_, cur = heapq.heappop(open_heap)
|
||||
if cur in goal_set:
|
||||
return _reconstruct_rooms(came, cur)
|
||||
base = g_score[cur]
|
||||
c_center = graph.rooms[cur].center()
|
||||
for door in graph.doors[cur]:
|
||||
step = _dist(c_center, graph.rooms[door.other].center())
|
||||
if door.kind == "via":
|
||||
step += via_cost
|
||||
tentative = base + step
|
||||
if tentative < g_score.get(door.other, float("inf")):
|
||||
came[door.other] = (cur, door)
|
||||
g_score[door.other] = tentative
|
||||
h = _dist(graph.rooms[door.other].center(), goal_pt)
|
||||
heapq.heappush(open_heap, (tentative + h, door.other))
|
||||
return None
|
||||
|
||||
|
||||
def _reconstruct_rooms(came, cur):
|
||||
seq = [(cur, None)]
|
||||
while cur in came:
|
||||
prev, door = came[cur]
|
||||
seq.append((prev, door))
|
||||
cur = prev
|
||||
seq.reverse()
|
||||
return seq # list of (room_id, door_used_to_enter_or_None_for_start)
|
||||
|
||||
|
||||
def _dist(a: IntPoint, b: IntPoint) -> float:
|
||||
return abs(a.x - b.x) + abs(a.y - b.y)
|
||||
|
||||
|
||||
# --- path realization --------------------------------------------------------
|
||||
|
||||
|
||||
def _realize(graph, seq, start: IntPoint, goal: IntPoint, half_width):
|
||||
"""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``.
|
||||
"""
|
||||
wires: list[tuple[int, list[IntPoint]]] = []
|
||||
vias: list[IntPoint] = []
|
||||
layer = graph.rooms[seq[0][0]].layer
|
||||
pts = [start]
|
||||
# seq[i] carries the door from room i to room i+1 (seq[-1] has None)
|
||||
for i in range(len(seq) - 1):
|
||||
door = seq[i][1]
|
||||
if door is None:
|
||||
return None
|
||||
if door.kind == "edge":
|
||||
gate = _edge_gate(door, pts[-1], half_width)
|
||||
pts.append(gate)
|
||||
else: # via: close the current-layer wire, drop a via, start next layer
|
||||
gate = _via_gate(door, pts[-1], half_width)
|
||||
pts.append(gate)
|
||||
wires.append((layer, list(pts)))
|
||||
vias.append(gate)
|
||||
layer = graph.rooms[door.other].layer
|
||||
pts = [gate]
|
||||
pts.append(goal)
|
||||
wires.append((layer, pts))
|
||||
# expand each polyline of gate points into orthogonal L-connected corners
|
||||
ortho: list[tuple[int, list[IntPoint]]] = []
|
||||
for lyr, poly in wires:
|
||||
corners = _orthogonalize(poly)
|
||||
if len(corners) >= 2:
|
||||
ortho.append((lyr, corners))
|
||||
return ortho, vias
|
||||
|
||||
|
||||
def _edge_gate(door: _Door, from_pt: IntPoint, half_width: int) -> IntPoint:
|
||||
"""A point on the door edge, kept >= half_width from the door ends."""
|
||||
if door.lo.x == door.hi.x: # vertical edge -> pick y
|
||||
y = _clamp(from_pt.y, door.lo.y + half_width, door.hi.y - half_width)
|
||||
return IntPoint(door.lo.x, y)
|
||||
x = _clamp(from_pt.x, door.lo.x + half_width, door.hi.x - half_width)
|
||||
return IntPoint(x, door.lo.y)
|
||||
|
||||
|
||||
def _via_gate(door: _Door, from_pt: IntPoint, half_width: int) -> IntPoint:
|
||||
x = _clamp(from_pt.x, door.lo.x + half_width, door.hi.x - half_width)
|
||||
y = _clamp(from_pt.y, door.lo.y + half_width, door.hi.y - half_width)
|
||||
return IntPoint(x, y)
|
||||
|
||||
|
||||
def _orthogonalize(points: list[IntPoint]) -> list[IntPoint]:
|
||||
"""Connect gate points with axis-aligned segments (L-shaped between each)."""
|
||||
out = [points[0]]
|
||||
for p in points[1:]:
|
||||
last = out[-1]
|
||||
if last.x != p.x and last.y != p.y:
|
||||
out.append(IntPoint(p.x, last.y)) # horizontal then vertical
|
||||
if not (out[-1].x == p.x and out[-1].y == p.y):
|
||||
out.append(p)
|
||||
return _dedupe(out)
|
||||
|
||||
|
||||
def _dedupe(points: list[IntPoint]) -> list[IntPoint]:
|
||||
out: list[IntPoint] = []
|
||||
for p in points:
|
||||
if out and out[-1].x == p.x and out[-1].y == p.y:
|
||||
continue
|
||||
if len(out) >= 2:
|
||||
a, b = out[-2], out[-1]
|
||||
if (b.x - a.x) * (p.y - a.y) - (b.y - a.y) * (p.x - a.x) == 0:
|
||||
out[-1] = p
|
||||
continue
|
||||
out.append(p)
|
||||
return out
|
||||
|
||||
|
||||
def _clamp(v: int, lo: int, hi: int) -> int:
|
||||
if lo > hi:
|
||||
return (lo + hi) // 2
|
||||
return max(lo, min(hi, v))
|
||||
|
||||
|
||||
# --- orchestration -----------------------------------------------------------
|
||||
|
||||
|
||||
def route_board_rooms(
|
||||
board: BasicBoard,
|
||||
*,
|
||||
trace_width: int,
|
||||
clearance: int,
|
||||
layers: list[int] | None = None,
|
||||
via_cost: float = 50000.0,
|
||||
) -> ExactRouteResult:
|
||||
"""Route ``board`` with the continuous expansion-room track (foundation)."""
|
||||
signal_layers = layers or [
|
||||
i for i, layer in enumerate(board.layer_structure.arr) if layer.is_signal
|
||||
]
|
||||
half_width = max(trace_width // 2, 1)
|
||||
margin = clearance + half_width
|
||||
outline = board.bounding_box
|
||||
|
||||
tree = ShapeSearchTree()
|
||||
owner = _counter()
|
||||
for pin in board.get_pins():
|
||||
if pin.shape is None:
|
||||
continue
|
||||
box = pin.shape.bounding_box()
|
||||
if box.is_empty():
|
||||
continue
|
||||
net = pin.net_nos[0] if pin.net_nos else -1
|
||||
pin_layers = frozenset(layer for layer in pin.layers if layer in signal_layers)
|
||||
if pin_layers:
|
||||
tree.insert(TreeShape(next(owner), net, pin_layers, box))
|
||||
for obstacle in board.get_obstacle_areas():
|
||||
if obstacle.layer not in signal_layers:
|
||||
continue
|
||||
for tile in obstacle.tiles:
|
||||
b = tile.bounding_box()
|
||||
if not b.is_empty():
|
||||
tree.insert(TreeShape(next(owner), -1, frozenset({obstacle.layer}), b))
|
||||
|
||||
result = RouteResult(half_width)
|
||||
via_layers = frozenset(signal_layers)
|
||||
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(
|
||||
net_no,
|
||||
a,
|
||||
b,
|
||||
tree,
|
||||
result,
|
||||
owner,
|
||||
outline,
|
||||
signal_layers,
|
||||
half_width,
|
||||
clearance,
|
||||
margin,
|
||||
trace_width,
|
||||
via_cost,
|
||||
via_layers,
|
||||
)
|
||||
|
||||
drc_clean = tree.has_violation(clearance) is None
|
||||
return ExactRouteResult(result=result, tree=tree, drc_clean=drc_clean)
|
||||
|
||||
|
||||
def _route_connection(
|
||||
net_no,
|
||||
a,
|
||||
b,
|
||||
tree,
|
||||
result,
|
||||
owner,
|
||||
outline,
|
||||
signal_layers,
|
||||
half_width,
|
||||
clearance,
|
||||
margin,
|
||||
trace_width,
|
||||
via_cost,
|
||||
via_layers,
|
||||
):
|
||||
rooms_by_layer = {
|
||||
layer: _free_rooms(tree, outline, layer, net_no, margin, half_width)
|
||||
for layer in signal_layers
|
||||
}
|
||||
graph = _build_graph(rooms_by_layer, trace_width)
|
||||
a_layers = [layer for layer in a.layers if layer in signal_layers]
|
||||
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
|
||||
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
|
||||
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
|
||||
|
||||
for box, box_layers in boxes:
|
||||
tree.insert(TreeShape(next(owner), net_no, box_layers, box, routed=True))
|
||||
for layer, corners in wires:
|
||||
result.add_wire(net_no, layer, corners)
|
||||
for loc in vias:
|
||||
result.add_via(net_no, loc)
|
||||
|
||||
|
||||
def _net_pins(board: BasicBoard, net_no: int, signal_layers) -> list[Pin]:
|
||||
return [
|
||||
pin
|
||||
for pin in board.get_pins()
|
||||
if net_no in pin.net_nos and any(layer in signal_layers for layer in pin.layers)
|
||||
]
|
||||
|
||||
|
||||
def _counter():
|
||||
i = 0
|
||||
while True:
|
||||
yield i
|
||||
i += 1
|
||||
Loading…
x
Reference in New Issue
Block a user