Rename to rpmroute

The router began as a port of FreeRouting and has grown its own engine
(negotiated congestion, tree nets, exact checks, axis escapes), and
'freeroute' was one letter from the project it no longer follows. The
package, module and command are now rpmroute; the freeroute command stays
as an alias, and the RPMROUTE / RPMROUTE_ORACLE_JAVA variables fall back
to the old FREEROUTE names. Version 2026.10.07. Routing is unchanged: a
PCBench board routes to a byte-identical session either way.
This commit is contained in:
2026-10-07 14:10:36 -06:00
parent 45aee463a8
commit a6aa1991a1
95 changed files with 346 additions and 334 deletions
+6 -6
View File
@@ -14,11 +14,11 @@ import math
import pytest
from freeroute.board import build_board
from freeroute.dsn import parse_dsn
from freeroute.geometry import IntPoint, overlaps_2d, segment_slab
from freeroute.route.grid_router import GridRouter
from freeroute.route.pipeline import (
from rpmroute.board import build_board
from rpmroute.dsn import parse_dsn
from rpmroute.geometry import IntPoint, overlaps_2d, segment_slab
from rpmroute.route.grid_router import GridRouter
from rpmroute.route.pipeline import (
_rule_clearance_dsn,
_rule_width_dsn,
_via_half_board,
@@ -107,7 +107,7 @@ def test_a_pad_on_the_grid_axes_keeps_the_usual_exits():
def test_a_slanted_slab_is_tight_where_a_bounding_box_is_not():
slab = segment_slab(IntPoint(0, 0), IntPoint(10000, 10000), 750)
# a point beside the diagonal but inside its bounding box
from freeroute.geometry import IntBox
from rpmroute.geometry import IntBox
assert not overlaps_2d(slab, IntBox(8000, 0, 8100, 100))
assert overlaps_2d(slab, IntBox(4900, 4900, 5100, 5100))
+6 -6
View File
@@ -16,11 +16,11 @@ from pathlib import Path
import pytest
from freeroute.board import build_board
from freeroute.dsn import parse_dsn
from freeroute.dsn.sexp import parse
from freeroute.geometry import IntBox, IntPoint, overlaps_2d, segment_octagon
from freeroute.route import route, route_dsn_board_exact
from rpmroute.board import build_board
from rpmroute.dsn import parse_dsn
from rpmroute.dsn.sexp import parse
from rpmroute.geometry import IntBox, IntPoint, overlaps_2d, segment_octagon
from rpmroute.route import route, route_dsn_board_exact
FIXTURES = Path(__file__).resolve().parent.parent / "dsn" / "fixtures"
DIAGONAL_WIN = FIXTURES / "diagonal_win.dsn"
@@ -55,7 +55,7 @@ def _pins_by_net(fixture):
def _rule_clearance_board(fixture):
from freeroute.route.pipeline import _rule_clearance_dsn
from rpmroute.route.pipeline import _rule_clearance_dsn
dsn = parse_dsn(fixture.read_text())
return round(_rule_clearance_dsn(dsn) * max(dsn.resolution.value, 1))
+6 -6
View File
@@ -15,12 +15,12 @@ import sys
import pytest
from freeroute.board import ShapeSearchTree, TreeShape, build_board
from freeroute.dsn import parse_dsn
from freeroute.dsn.sexp import parse
from freeroute.geometry import IntBox, Polyline, PolylineShape
from freeroute.route import route, route_dsn_board, route_dsn_board_exact
from freeroute.route.pipeline import _rule_clearance_dsn, _rule_width_dsn
from rpmroute.board import ShapeSearchTree, TreeShape, build_board
from rpmroute.dsn import parse_dsn
from rpmroute.dsn.sexp import parse
from rpmroute.geometry import IntBox, Polyline, PolylineShape
from rpmroute.route import route, route_dsn_board, route_dsn_board_exact
from rpmroute.route.pipeline import _rule_clearance_dsn, _rule_width_dsn
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
+5 -5
View File
@@ -2,9 +2,9 @@
from __future__ import annotations
from freeroute.board import build_board
from freeroute.dsn import parse_dsn
from freeroute.route.pipeline import route_dsn_board_exact
from rpmroute.board import build_board
from rpmroute.dsn import parse_dsn
from rpmroute.route.pipeline import route_dsn_board_exact
_DSN = """(pcb wired (parser) (resolution um 10) (unit um)
(structure
@@ -95,9 +95,9 @@ def test_a_fanned_out_signal_is_routed_from_its_via():
def test_the_session_never_repeats_copper_already_on_the_board():
# the JAR writes the DSN's wiring back into its session, and an applier
# then adds it a second time on top of itself; freeroute writes only
# then adds it a second time on top of itself; rpmroute writes only
# what it routed
from freeroute.route.pipeline import build_exact_routing_result
from rpmroute.route.pipeline import build_exact_routing_result
fixed_vias = {(5000.0, -11500.0), (10000.0, -11500.0)}
fixed_paths = {(5000.0, -10000.0, 5000.0, -11500.0), (10000.0, -10000.0, 10000.0, -11500.0),
+8 -8
View File
@@ -12,11 +12,11 @@ import math
import pytest
from freeroute.board import build_board
from freeroute.dsn import parse_dsn
from freeroute.geometry import IntBox, Simplex
from freeroute.route.grid_router import GridRouter
from freeroute.route.pipeline import (
from rpmroute.board import build_board
from rpmroute.dsn import parse_dsn
from rpmroute.geometry import IntBox, Simplex
from rpmroute.route.grid_router import GridRouter
from rpmroute.route.pipeline import (
_rule_clearance_dsn,
_rule_width_dsn,
_via_half_board,
@@ -268,9 +268,9 @@ def test_a_via_by_a_chamfered_corner_is_judged_round():
"""The grid keeps a via's centre its radius plus the edge clearance from
the outline. The exact check used the via's square, whose corner reaches
past a 45-degree chamfer where the round via does not."""
from freeroute.board.outline import BoardOutline
from freeroute.geometry import IntBox, IntPoint
from freeroute.route.exact_router import _inside_outline
from rpmroute.board.outline import BoardOutline
from rpmroute.geometry import IntBox, IntPoint
from rpmroute.route.exact_router import _inside_outline
# a 10 mm square with its lower-left corner cut at 45 degrees
outline = BoardOutline([IntPoint(*p) for p in
+4 -4
View File
@@ -6,10 +6,10 @@ from pathlib import Path
import pytest
from freeroute.board import build_board
from freeroute.dsn import parse_dsn
from freeroute.route.grid_router import GridRouter
from freeroute.route.pipeline import _rule_clearance_dsn, _rule_width_dsn, route_dsn_board
from rpmroute.board import build_board
from rpmroute.dsn import parse_dsn
from rpmroute.route.grid_router import GridRouter
from rpmroute.route.pipeline import _rule_clearance_dsn, _rule_width_dsn, route_dsn_board
FIXTURES = Path(__file__).resolve().parent.parent / "dsn" / "fixtures"
+2 -2
View File
@@ -2,8 +2,8 @@
from __future__ import annotations
from freeroute.dsn import parse_dsn
from freeroute.route.pipeline import build_exact_routing_result
from rpmroute.dsn import parse_dsn
from rpmroute.route.pipeline import build_exact_routing_result
_BOARD = """(pcb widths (parser) (resolution um 10) (unit um)
(structure
+2 -2
View File
@@ -4,8 +4,8 @@ from __future__ import annotations
from pathlib import Path
from freeroute.cli import main
from freeroute.route.orders import (
from rpmroute.cli import main
from rpmroute.route.orders import (
best_of_orders,
candidate_seeds,
promote_failures,
+8 -8
View File
@@ -6,9 +6,9 @@ import math
import pytest
from freeroute.board import build_board
from freeroute.dsn import parse_dsn
from freeroute.route.pipeline import route_dsn_board_exact
from rpmroute.board import build_board
from rpmroute.dsn import parse_dsn
from rpmroute.route.pipeline import route_dsn_board_exact
# 20 x 20 mm board whose corners are rounded with a 3 mm radius, as polylines
_R = 3000.0
@@ -70,7 +70,7 @@ def test_every_pin_of_a_half_millimetre_pitch_row_escapes(resolution):
def test_resolution_one_has_no_halo_so_the_grid_is_unchanged():
from freeroute.route.grid_router import GridRouter
from rpmroute.route.grid_router import GridRouter
board = build_board(parse_dsn(_dsn("", "")))
assert GridRouter(board, trace_width=2500, clearance=1500)._halo_offsets == []
@@ -101,7 +101,7 @@ def test_heuristic_weight_bounds_route_length():
# --- copper zones (DSN planes) -----------------------------------------------
# rc4 power stage: pads inside "keep router off" pours connect through the pour,
# and freeroute, not knowing zones, dropped every such connection.
# and rpmroute, not knowing zones, dropped every such connection.
_ZONE = "(plane N (polygon F.Cu 0 4000 -4000 10000 -4000 10000 -10000 4000 -10000 4000 -4000))"
_NO_TRACKS = "(wire_keepout \"pour\" (polygon F.Cu 0 4000 -4000 10000 -4000 10000 -10000 4000 -10000))"
@@ -121,7 +121,7 @@ def test_planes_are_read_and_are_not_obstacles():
def test_pads_in_one_zone_need_no_route_between_them():
from freeroute.route.grid_router import _connected_groups
from rpmroute.route.grid_router import _connected_groups
board = build_board(_zone_board())
pins = sorted(board.get_pins(), key=lambda p: p.component_ref)
@@ -155,7 +155,7 @@ def test_a_pour_other_nets_can_cross_is_not_trusted_to_connect():
def test_cli_names_the_connections_it_could_not_route(tmp_path, capsys):
# a keepout wall across the board on both layers: A and B cannot be joined
from freeroute.cli import main
from rpmroute.cli import main
wall = " ".join(
f"(keepout \"wall\" (polygon {layer} 0 9000 1000 11000 1000 11000 -21000 9000 -21000))"
@@ -218,7 +218,7 @@ def test_a_pad_outside_the_plane_routes_to_it():
def test_a_through_hole_pad_inside_a_plane_needs_no_route():
from freeroute.route.grid_router import _connected_groups
from rpmroute.route.grid_router import _connected_groups
board = build_board(parse_dsn(_four_layer_dsn()))
plane = [z for z in board.get_conduction_areas() if z.name == "GND"]
+4 -4
View File
@@ -6,10 +6,10 @@ from pathlib import Path
import pytest
from freeroute.board import build_board
from freeroute.dsn import parse_dsn
from freeroute.route.grid_router import GridRouter
from freeroute.route.pipeline import _rule_clearance_dsn, _rule_width_dsn, route_dsn_board
from rpmroute.board import build_board
from rpmroute.dsn import parse_dsn
from rpmroute.route.grid_router import GridRouter
from rpmroute.route.pipeline import _rule_clearance_dsn, _rule_width_dsn, route_dsn_board
FIXTURES = Path(__file__).resolve().parent.parent / "dsn" / "fixtures"
+6 -6
View File
@@ -23,11 +23,11 @@ from pathlib import Path
import pytest
from freeroute.dsn import parse_dsn
from freeroute.dsn.sexp import parse
from freeroute.geometry import Polyline, PolylineShape
from freeroute.route import route, route_dsn_board_rooms
from freeroute.route.pipeline import _rule_clearance_dsn
from rpmroute.dsn import parse_dsn
from rpmroute.dsn.sexp import parse
from rpmroute.geometry import Polyline, PolylineShape
from rpmroute.route import route, route_dsn_board_rooms
from rpmroute.route.pipeline import _rule_clearance_dsn
FIXTURES = Path(__file__).resolve().parent.parent / "dsn" / "fixtures"
CHANNEL_PACK = FIXTURES / "channel_pack.dsn"
@@ -134,7 +134,7 @@ def test_channel_pack_is_deterministic():
def test_channel_pack_endpoints_stay_on_pads():
dsn = parse_dsn(CHANNEL_PACK.read_text())
board_pins: dict[int, set[tuple[int, int]]] = {}
from freeroute.board import build_board
from rpmroute.board import build_board
for pin in build_board(dsn).get_pins():
for net_no in pin.net_nos:
+6 -6
View File
@@ -13,11 +13,11 @@ import sys
import pytest
from freeroute.board import build_board
from freeroute.dsn import parse_dsn
from freeroute.dsn.sexp import parse
from freeroute.geometry import Polyline
from freeroute.route import route, route_dsn_board_exact, route_dsn_board_rooms
from rpmroute.board import build_board
from rpmroute.dsn import parse_dsn
from rpmroute.dsn.sexp import parse
from rpmroute.geometry import Polyline
from rpmroute.route import route, route_dsn_board_exact, route_dsn_board_rooms
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
@@ -31,7 +31,7 @@ WIDE_DOOR = FIXTURES / "rooms_wide_door.dsn"
def _clearance(dsn, scale):
from freeroute.route.pipeline import _rule_clearance_dsn
from rpmroute.route.pipeline import _rule_clearance_dsn
return round(_rule_clearance_dsn(dsn) * scale)
+11 -11
View File
@@ -32,12 +32,12 @@ import sys
import pytest
from freeroute.board import build_board
from freeroute.dsn import parse_dsn
from freeroute.dsn.sexp import parse
from freeroute.geometry import IntBox, IntPoint, Polyline, PolylineShape
from freeroute.route import route, route_dsn_board_exact, route_dsn_board_rooms
from freeroute.route.pipeline import _rule_clearance_dsn
from rpmroute.board import build_board
from rpmroute.dsn import parse_dsn
from rpmroute.dsn.sexp import parse
from rpmroute.geometry import IntBox, IntPoint, Polyline, PolylineShape
from rpmroute.route import route, route_dsn_board_exact, route_dsn_board_rooms
from rpmroute.route.pipeline import _rule_clearance_dsn
def _emitted_drc_violations(result, clearance):
@@ -265,8 +265,8 @@ def test_shove_is_byte_identical_on_foundation_fixtures(fixture):
def test_project_gate_reduces_to_clamp_without_occupancy():
from freeroute.board.search_tree import ShapeSearchTree
from freeroute.route.room_router import _Door, _edge_gate, _project_gate
from rpmroute.board.search_tree import ShapeSearchTree
from rpmroute.route.room_router import _Door, _edge_gate, _project_gate
door = _Door(other=1, kind="edge", lo=IntPoint(0, 100), hi=IntPoint(200, 100))
tree = ShapeSearchTree()
@@ -277,8 +277,8 @@ def test_project_gate_reduces_to_clamp_without_occupancy():
def test_project_gate_avoids_other_net_copper_on_the_door():
from freeroute.board.search_tree import ShapeSearchTree, TreeShape
from freeroute.route.room_router import _Door, _project_gate
from rpmroute.board.search_tree import ShapeSearchTree, TreeShape
from rpmroute.route.room_router import _Door, _project_gate
door = _Door(other=1, kind="edge", lo=IntPoint(0, 100), hi=IntPoint(200, 100))
tree = ShapeSearchTree()
@@ -293,7 +293,7 @@ def test_project_gate_avoids_other_net_copper_on_the_door():
def test_free_intervals_splits_around_forbidden_band():
from freeroute.route.room_router import _best_free_point, _free_intervals
from rpmroute.route.room_router import _best_free_point, _free_intervals
free = _free_intervals(0, 200, [(87, 113)])
assert free == [(0, 87), (113, 200)]
+8 -8
View File
@@ -16,11 +16,11 @@ import sys
import pytest
from freeroute.board import build_board
from freeroute.dsn import parse_dsn
from freeroute.dsn.sexp import parse
from freeroute.route import route, route_dsn_board
from freeroute.ses.writer import ses_scale
from rpmroute.board import build_board
from rpmroute.dsn import parse_dsn
from rpmroute.dsn.sexp import parse
from rpmroute.route import route, route_dsn_board
from rpmroute.ses.writer import ses_scale
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
@@ -287,11 +287,11 @@ def test_connectivity_parity_on_congested_board():
if not HAS_ORACLE:
pytest.skip("FreeRouting oracle unavailable")
our_nets = routed_net_set(route(RIPUP.read_text()))
assert our_nets == {"NET_A", "NET_B"} # freeroute connects both via rip-up
assert our_nets == {"NET_A", "NET_B"} # rpmroute connects both via rip-up
oracle_nets = routed_net_set(route_dsn(RIPUP, max_passes=3, timeout=300))
if not oracle_nets:
# the reference JAR does not route this synthetic single-signal-layer
# board (it expects a routing/via layer); freeroute's rip-up connecting
# board (it expects a routing/via layer); rpmroute's rip-up connecting
# both nets is already asserted above and by the fast tests.
pytest.skip("reference JAR routed nothing on the synthetic single-layer board")
assert oracle_nets <= our_nets # rip-up matches the JAR's connectivity
@@ -306,7 +306,7 @@ def test_connectivity_parity_on_crossing_board():
our_nets = routed_net_set(route(CROSSING.read_text()))
oracle_nets = routed_net_set(route_dsn(CROSSING, max_passes=3, timeout=300))
assert oracle_nets, "oracle routed nothing on a routable board"
assert oracle_nets <= our_nets # freeroute connects every net the JAR does
assert oracle_nets <= our_nets # rpmroute connects every net the JAR does
@pytest.mark.oracle
+5 -5
View File
@@ -17,10 +17,10 @@ import sys
import pytest
from freeroute.dsn import parse_dsn
from freeroute.dsn.sexp import parse
from freeroute.route import route, route_dsn_board_exact
from freeroute.route.pipeline import _rule_clearance_dsn
from rpmroute.dsn import parse_dsn
from rpmroute.dsn.sexp import parse
from rpmroute.route import route, route_dsn_board_exact
from rpmroute.route.pipeline import _rule_clearance_dsn
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
@@ -66,7 +66,7 @@ def test_shove_result_is_deterministic():
def test_shoved_trace_endpoints_still_on_pads():
from freeroute.board import build_board
from rpmroute.board import build_board
on, _, _ = route_dsn_board_exact(parse_dsn(SHOVE.read_text()), rip_up=False, shove=True)
board = build_board(parse_dsn(SHOVE.read_text()))
+6 -6
View File
@@ -8,12 +8,12 @@ from __future__ import annotations
import pytest
from freeroute.board import build_board
from freeroute.board.items import Pin
from freeroute.dsn import parse_dsn
from freeroute.geometry import IntPoint
from freeroute.route.grid_router import GridRouter
from freeroute.route.pipeline import route_dsn_board_exact
from rpmroute.board import build_board
from rpmroute.board.items import Pin
from rpmroute.dsn import parse_dsn
from rpmroute.geometry import IntPoint
from rpmroute.route.grid_router import GridRouter
from rpmroute.route.pipeline import route_dsn_board_exact
def _row(x0: int, pins: int = 6, skew: int = 3000) -> str:
+5 -5
View File
@@ -5,11 +5,11 @@ from __future__ import annotations
import pytest
from freeroute.board import build_board
from freeroute.dsn import parse_dsn
from freeroute.route import exact_router
from freeroute.route.grid_router import GridRouter, RouteResult
from freeroute.route.pipeline import _rule_clearance_dsn, _rule_width_dsn
from rpmroute.board import build_board
from rpmroute.dsn import parse_dsn
from rpmroute.route import exact_router
from rpmroute.route.grid_router import GridRouter, RouteResult
from rpmroute.route.pipeline import _rule_clearance_dsn, _rule_width_dsn
# P1 and P2 are nearest each other, so the tree pairs them first; P3 sits
# under the middle of that leg, 50 mm below it and 90 mm from either pin.
+6 -6
View File
@@ -5,8 +5,8 @@ from __future__ import annotations
import itertools
import math
from freeroute.dsn import parse_dsn
from freeroute.route.pipeline import route_dsn_board_exact
from rpmroute.dsn import parse_dsn
from rpmroute.route.pipeline import route_dsn_board_exact
# four GND pads 0.5 mm apart over a GND plane: each drops a via to the plane
_DSN = """(pcb holes (parser) (resolution um 10) (unit um)
@@ -57,9 +57,9 @@ def test_connections_sharing_a_plane_via_write_it_once():
def _router(dsn_text, via_drill_um=300):
from freeroute.board import build_board
from freeroute.route.grid_router import GridRouter
from freeroute.route.pipeline import _rule_clearance_dsn, _rule_width_dsn
from rpmroute.board import build_board
from rpmroute.route.grid_router import GridRouter
from rpmroute.route.pipeline import _rule_clearance_dsn, _rule_width_dsn
dsn = parse_dsn(dsn_text)
s = dsn.resolution.value
@@ -90,7 +90,7 @@ def test_no_via_goes_near_a_through_hole_pads_hole_even_of_its_own_net():
def test_a_via_in_a_pad_keeps_hole_spacing_from_a_fixed_via():
from freeroute.geometry import IntPoint
from rpmroute.geometry import IntPoint
text = _THT.replace("(network (net SIG (pins J1-1))))",
"(network (net SIG (pins J1-1)))\n"
+7 -7
View File
@@ -3,11 +3,11 @@ it leaves fewer connections unrouted."""
from __future__ import annotations
from freeroute.board import build_board
from freeroute.dsn import parse_dsn
from freeroute.geometry import IntBox
from freeroute.route.grid_router import GridRouter
from freeroute.route.pipeline import _rule_clearance_dsn, _rule_width_dsn, route_dsn_board_exact
from rpmroute.board import build_board
from rpmroute.dsn import parse_dsn
from rpmroute.geometry import IntBox
from rpmroute.route.grid_router import GridRouter
from rpmroute.route.pipeline import _rule_clearance_dsn, _rule_width_dsn, route_dsn_board_exact
def _slot(gap: int = 640, far: bool = False) -> str:
@@ -101,7 +101,7 @@ def test_a_spent_budget_starts_no_window():
def test_window_board_keeps_far_items_of_the_rerouted_nets_only():
from freeroute.route.window import _item_box, _window_board
from rpmroute.route.window import _item_box, _window_board
board = build_board(parse_dsn(_slot(far=True)))
items = [(item, _item_box(item)) for item in board.get_items()]
@@ -115,7 +115,7 @@ def test_window_board_keeps_far_items_of_the_rerouted_nets_only():
def test_a_connection_ending_outside_the_window_gets_no_copper():
"""Its far end has no grid cells; a stub out to it would cross items the
window board never saw."""
from freeroute.route.exact_router import route_board_exact
from rpmroute.route.exact_router import route_board_exact
dsn = parse_dsn(_slot(far=True))
s = dsn.resolution.value