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:
@@ -2,7 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from freeroute.board import (
|
||||
from rpmroute.board import (
|
||||
ClearanceMatrix,
|
||||
CoordinateTransform,
|
||||
Layer,
|
||||
|
||||
@@ -15,9 +15,9 @@ import sys
|
||||
|
||||
import pytest
|
||||
|
||||
from freeroute.board import BasicBoard, ObstacleArea, Pin, build_board
|
||||
from freeroute.dsn import parse_dsn
|
||||
from freeroute.geometry import IntPoint
|
||||
from rpmroute.board import BasicBoard, ObstacleArea, Pin, build_board
|
||||
from rpmroute.dsn import parse_dsn
|
||||
from rpmroute.geometry import IntPoint
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
|
||||
@@ -184,7 +184,7 @@ def test_place_keepout_does_not_block_routing():
|
||||
|
||||
|
||||
def test_grid_router_leaves_the_interior_of_a_ring_keepout_open():
|
||||
from freeroute.route.pipeline import route_dsn_board
|
||||
from rpmroute.route.pipeline import route_dsn_board
|
||||
|
||||
ring_only = "\n".join(
|
||||
line for line in _RING_DSN.splitlines() if "wire_keepout" not in line
|
||||
@@ -251,7 +251,7 @@ def test_oval_pad_keeps_its_width():
|
||||
|
||||
|
||||
def test_via_size_comes_from_the_via_padstack():
|
||||
from freeroute.route.pipeline import _via_half_board
|
||||
from rpmroute.route.pipeline import _via_half_board
|
||||
|
||||
assert _via_half_board(parse_dsn(_PAD_DSN), 10) == 3000 # 0.6 mm via, board units
|
||||
|
||||
@@ -279,7 +279,7 @@ def test_a_keepout_named_rule_area_takes_no_clearance():
|
||||
|
||||
|
||||
def _route_channel(name):
|
||||
from freeroute.route.pipeline import build_exact_routing_result
|
||||
from rpmroute.route.pipeline import build_exact_routing_result
|
||||
|
||||
return build_exact_routing_result(parse_dsn(_CHANNEL_DSN.format(name=name)), resolution=2)
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from freeroute.board import ShapeSearchTree, TreeShape
|
||||
from freeroute.geometry import IntBox
|
||||
from rpmroute.board import ShapeSearchTree, TreeShape
|
||||
from rpmroute.geometry import IntBox
|
||||
|
||||
|
||||
def shape(owner, net, layer, box, routed=False):
|
||||
|
||||
@@ -6,10 +6,10 @@ from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from freeroute.dsn import parse_dsn
|
||||
from freeroute.dsn.reader import DsnParseError
|
||||
from freeroute.dsn.shapes import Circle, Rectangle
|
||||
from freeroute.dsn.shapes import Path as DsnPath
|
||||
from rpmroute.dsn import parse_dsn
|
||||
from rpmroute.dsn.reader import DsnParseError
|
||||
from rpmroute.dsn.shapes import Circle, Rectangle
|
||||
from rpmroute.dsn.shapes import Path as DsnPath
|
||||
|
||||
FIXTURES = Path(__file__).parent / "fixtures"
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from freeroute.dsn.sexp import SExp, parse
|
||||
from freeroute.dsn.tokenizer import DsnSyntaxError, Token
|
||||
from rpmroute.dsn.sexp import SExp, parse
|
||||
from rpmroute.dsn.tokenizer import DsnSyntaxError, Token
|
||||
|
||||
|
||||
def test_parse_simple_head_and_values():
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from freeroute.dsn.sexp import parse
|
||||
from freeroute.dsn.shapes import (
|
||||
from rpmroute.dsn.sexp import parse
|
||||
from rpmroute.dsn.shapes import (
|
||||
Circle,
|
||||
Path,
|
||||
Polygon,
|
||||
|
||||
@@ -4,7 +4,7 @@ from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from freeroute.dsn.tokenizer import DsnSyntaxError, TokenKind, tokenize
|
||||
from rpmroute.dsn.tokenizer import DsnSyntaxError, TokenKind, tokenize
|
||||
|
||||
|
||||
def kinds(text: str) -> list[TokenKind]:
|
||||
|
||||
@@ -6,7 +6,7 @@ containment and degenerate (empty / point / segment) tiles.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from freeroute.geometry import IntBox, IntPoint, IntVector, RationalPoint
|
||||
from rpmroute.geometry import IntBox, IntPoint, IntVector, RationalPoint
|
||||
|
||||
|
||||
def test_measures():
|
||||
|
||||
@@ -5,7 +5,7 @@ Oracles hand-derived from ``geometry/planar/{Direction,IntDirection}.java``.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from freeroute.geometry import Direction, IntDirection, IntPoint, IntVector, Side
|
||||
from rpmroute.geometry import Direction, IntDirection, IntPoint, IntVector, Side
|
||||
|
||||
|
||||
def test_normalized_direction_reduces_by_gcd():
|
||||
|
||||
@@ -5,7 +5,7 @@ Oracles hand-derived from ``geometry/planar/Line.java``.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from freeroute.geometry import IntPoint, Line, RationalPoint, Side
|
||||
from rpmroute.geometry import IntPoint, Line, RationalPoint, Side
|
||||
|
||||
|
||||
def test_side_of_point():
|
||||
@@ -79,7 +79,7 @@ def test_intersection_approx_matches_exact():
|
||||
def test_translate_and_opposite():
|
||||
line = Line.from_coords(0, 0, 4, 0)
|
||||
assert line.opposite().direction().equals(line.direction()) is False
|
||||
from freeroute.geometry import IntVector
|
||||
from rpmroute.geometry import IntVector
|
||||
|
||||
moved = line.translate_by(IntVector(0, 5))
|
||||
assert moved.side_of(IntPoint(2, 5)) == Side.COLLINEAR
|
||||
|
||||
@@ -7,7 +7,7 @@ import random
|
||||
|
||||
import pytest
|
||||
|
||||
from freeroute.geometry import IntBox, IntPoint, ceil_sqrt2, overlaps_2d, segment_octagon
|
||||
from rpmroute.geometry import IntBox, IntPoint, ceil_sqrt2, overlaps_2d, segment_octagon
|
||||
|
||||
|
||||
@pytest.mark.parametrize("r", [0, 1, 2, 3, 1000, 2000, 3000, 12345, 999999])
|
||||
|
||||
@@ -8,7 +8,7 @@ Java ``long``, and rational equality.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from freeroute.geometry import (
|
||||
from rpmroute.geometry import (
|
||||
IntPoint,
|
||||
IntVector,
|
||||
RationalPoint,
|
||||
@@ -139,7 +139,7 @@ def test_rational_point_infinite():
|
||||
|
||||
|
||||
def test_rational_point_contained_in_box_exact():
|
||||
from freeroute.geometry import IntBox
|
||||
from rpmroute.geometry import IntBox
|
||||
|
||||
box = IntBox(0, 0, 2, 2)
|
||||
assert RationalPoint(3, 2, 2).is_contained_in(box) # (1.5, 1.0) inside
|
||||
|
||||
@@ -13,9 +13,9 @@ from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from freeroute.geometry import IntPoint, Side
|
||||
from freeroute.geometry.polygon import Polygon
|
||||
from freeroute.geometry.polygon_shape import PolygonShape
|
||||
from rpmroute.geometry import IntPoint, Side
|
||||
from rpmroute.geometry.polygon import Polygon
|
||||
from rpmroute.geometry.polygon_shape import PolygonShape
|
||||
|
||||
|
||||
def pts(*coords):
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from freeroute.geometry import (
|
||||
from rpmroute.geometry import (
|
||||
IntBox,
|
||||
IntPoint,
|
||||
Polyline,
|
||||
|
||||
@@ -16,7 +16,7 @@ Source: ``geometry/planar/{TileShape,Simplex}.java``.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from freeroute.geometry import IntBox, IntPoint, Line, Side, Simplex
|
||||
from rpmroute.geometry import IntBox, IntPoint, Line, Side, Simplex
|
||||
|
||||
|
||||
def _grid(lo: int, hi: int):
|
||||
@@ -162,7 +162,7 @@ def test_get_instance_detects_empty_from_opposing_halfplanes():
|
||||
|
||||
def test_translate_preserves_shape():
|
||||
tri = right_triangle()
|
||||
from freeroute.geometry import IntVector
|
||||
from rpmroute.geometry import IntVector
|
||||
|
||||
moved = tri.translate_by(IntVector(100, 50))
|
||||
for p in _grid(-3, 13):
|
||||
|
||||
+5
-5
@@ -1,8 +1,8 @@
|
||||
"""FreeRouting JAR oracle for behaviour-level validation.
|
||||
|
||||
**Dev/test only — not part of the shipped package** (freeroute is Java-free by
|
||||
**Dev/test only — not part of the shipped package** (rpmroute is Java-free by
|
||||
design). This runs the reference FreeRouting JAR to route a Specctra DSN so that
|
||||
freeroute's own output can be diffed against the reference implementation. There
|
||||
rpmroute's own output can be diffed against the reference implementation. There
|
||||
are no unit-test oracles for FreeRouting's geometry/router, so the oracle is how
|
||||
we validate at the behaviour level: same DSN in, compare routed connectivity.
|
||||
|
||||
@@ -10,7 +10,7 @@ Tests that use this skip automatically when a Java 21+ runtime or the JAR is not
|
||||
available (e.g. CI without a JVM), so the normal suite stays Java-free.
|
||||
|
||||
Overrides:
|
||||
- ``FREEROUTE_ORACLE_JAVA`` — explicit path to a ``java`` binary.
|
||||
- ``RPMROUTE_ORACLE_JAVA`` (or the older ``FREEROUTE_ORACLE_JAVA``) — explicit path to a ``java`` binary.
|
||||
- ``FREEROUTING_JAR`` — explicit path to a freerouting JAR.
|
||||
"""
|
||||
|
||||
@@ -27,7 +27,7 @@ import tempfile
|
||||
|
||||
import pytest
|
||||
|
||||
from freeroute.dsn.sexp import parse
|
||||
from rpmroute.dsn.sexp import parse
|
||||
|
||||
_MIN_JAVA_MAJOR = 21
|
||||
_REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
@@ -60,7 +60,7 @@ def _java_major(java: str) -> int:
|
||||
def find_java() -> str | None:
|
||||
"""Return a path to a Java >= 21 runtime, or None."""
|
||||
candidates: list[str] = []
|
||||
override = os.environ.get("FREEROUTE_ORACLE_JAVA")
|
||||
override = os.environ.get("RPMROUTE_ORACLE_JAVA") or os.environ.get("FREEROUTE_ORACLE_JAVA")
|
||||
if override:
|
||||
candidates.append(override)
|
||||
java_home = os.environ.get("JAVA_HOME")
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()))
|
||||
|
||||
@@ -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,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.
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from freeroute.ses.indent import Identifier, IndentWriter
|
||||
from rpmroute.ses.indent import Identifier, IndentWriter
|
||||
|
||||
|
||||
def test_nested_scopes_indent_by_two_spaces():
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""End-to-end tests for the SES writer.
|
||||
|
||||
Where possible these re-parse the emitted SES with the project's own tokenizer /
|
||||
S-expression parser (:mod:`freeroute.dsn.sexp`) and assert structural
|
||||
S-expression parser (:mod:`rpmroute.dsn.sexp`) and assert structural
|
||||
invariants, mirroring what FreeRouting's ``SesReader`` and ``kicad-cli pcb
|
||||
import specctra-ses`` walk: ``session > routes > network_out > net >
|
||||
wire(path)/via``.
|
||||
@@ -13,9 +13,9 @@ from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from freeroute.dsn import parse_dsn
|
||||
from freeroute.dsn.sexp import SExp, parse
|
||||
from freeroute.ses import RoutedVia, RoutedWire, RoutingResult, write_ses
|
||||
from rpmroute.dsn import parse_dsn
|
||||
from rpmroute.dsn.sexp import SExp, parse
|
||||
from rpmroute.ses import RoutedVia, RoutedWire, RoutingResult, write_ses
|
||||
|
||||
FIXTURES = Path(__file__).parent.parent / "dsn" / "fixtures"
|
||||
|
||||
@@ -142,7 +142,7 @@ def test_round_trip_from_empty_board_fixture():
|
||||
|
||||
# --- SES coordinate units ---------------------------------------------------
|
||||
# FreeRouting's SesWriter writes coordinates in resolution units, and KiCad's
|
||||
# importer and mckicad's ses_apply read them that way. On 2026-09-26 freeroute
|
||||
# importer and mckicad's ses_apply read them that way. On 2026-09-26 rpmroute
|
||||
# was found writing DSN units instead, so every routed board came back 10x small.
|
||||
|
||||
|
||||
@@ -155,8 +155,8 @@ def test_round_trip_from_empty_board_fixture():
|
||||
],
|
||||
)
|
||||
def test_ses_scale(resolution, unit, expected):
|
||||
from freeroute.dsn import parse_dsn
|
||||
from freeroute.ses.writer import ses_scale
|
||||
from rpmroute.dsn import parse_dsn
|
||||
from rpmroute.ses.writer import ses_scale
|
||||
|
||||
dsn = parse_dsn(f"(pcb t (parser) {resolution} {unit} (structure) (placement) (library) (network))")
|
||||
assert ses_scale(dsn) == pytest.approx(expected)
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
"""Tests for the freeroute CLI (Specctra DSN in, SES out).
|
||||
"""Tests for the rpmroute CLI (Specctra DSN in, SES out).
|
||||
|
||||
Covers the engine selection contract: every engine must route a fixture from the
|
||||
command line, and every option the selected engine does not implement must fail
|
||||
@@ -11,7 +11,7 @@ from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from freeroute.cli import ENGINE_OPTIONS, main
|
||||
from rpmroute.cli import ENGINE_OPTIONS, main
|
||||
|
||||
FIXTURES = Path(__file__).parent / "dsn" / "fixtures"
|
||||
FIXTURE = FIXTURES / "simple_2net.dsn"
|
||||
|
||||
Reference in New Issue
Block a user