Some checks failed
CI / Lint and Format (push) Has been cancelled
CI / Test Python 3.11 on macos-latest (push) Has been cancelled
CI / Test Python 3.12 on macos-latest (push) Has been cancelled
CI / Test Python 3.13 on macos-latest (push) Has been cancelled
CI / Test Python 3.10 on ubuntu-latest (push) Has been cancelled
CI / Test Python 3.11 on ubuntu-latest (push) Has been cancelled
CI / Test Python 3.12 on ubuntu-latest (push) Has been cancelled
CI / Test Python 3.13 on ubuntu-latest (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / Build Package (push) Has been cancelled
freeroute's CLI defaults to the grid MVP, so mckicad was getting its weakest router: the continuous room engine (routes channels the grid cannot, emits exactly DRC-clean geometry) and channel packing (fits traces a greedy pass drops) were unreachable. Probe 'freeroute --help' once per CLI path and select the best engine the build advertises, defaulting to --engine room --pack. freeroute exits 2 on an unsupported (engine, flag) pair rather than ignoring it, so also gate each flag on the engine's own option matrix (packing is room-only; 45-degree routing is exact-track-only). An older freeroute that advertises no flags still gets the bare -de/-do drop-in call. All of it is overridable via routing_config.
83 lines
2.6 KiB
Python
83 lines
2.6 KiB
Python
"""mckicad selects freeroute's best *available* engine, not its grid default.
|
|
|
|
freeroute's CLI exits 2 on an unsupported (engine, flag) combination rather than
|
|
ignoring it, and older freeroute builds don't advertise the flags at all — so the
|
|
command has to be built from both the CLI's probed capabilities and the engine's
|
|
own option matrix.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from mckicad.utils.freerouting import FreeRoutingEngine
|
|
|
|
ALL_FLAGS = {"--engine", "--pack", "--shove", "--diagonal"}
|
|
|
|
|
|
def _run_capturing(captured):
|
|
def _run(cmd, **_kwargs):
|
|
captured.append(cmd)
|
|
out = cmd[cmd.index("-do") + 1]
|
|
with open(out, "w") as f:
|
|
f.write("(session board)")
|
|
return MagicMock(returncode=0, stdout="", stderr="")
|
|
|
|
return _run
|
|
|
|
|
|
def _route(tmp_path, supported, config=None):
|
|
engine = FreeRoutingEngine()
|
|
dsn = tmp_path / "board.dsn"
|
|
dsn.write_text("(pcb)")
|
|
captured: list[list[str]] = []
|
|
with (
|
|
patch("mckicad.utils.freerouting.freeroute_supported_flags", return_value=supported),
|
|
patch("mckicad.utils.freerouting.subprocess.run", side_effect=_run_capturing(captured)),
|
|
):
|
|
ok, ses = engine._run_freeroute_cli(
|
|
"/usr/bin/freeroute", str(dsn), str(tmp_path), config
|
|
)
|
|
assert ok and ses
|
|
return captured[0]
|
|
|
|
|
|
def test_defaults_to_room_engine_with_packing(tmp_path):
|
|
cmd = _route(tmp_path, ALL_FLAGS)
|
|
assert "--engine" in cmd and cmd[cmd.index("--engine") + 1] == "room"
|
|
assert "--pack" in cmd
|
|
# 45-degree routing is an exact-track feature; sending it with room would
|
|
# make freeroute exit 2.
|
|
assert "--diagonal" not in cmd
|
|
|
|
|
|
def test_older_freeroute_gets_the_bare_invocation(tmp_path):
|
|
cmd = _route(tmp_path, set()) # a build that advertises no optional flags
|
|
assert "--engine" not in cmd
|
|
assert "--pack" not in cmd
|
|
assert cmd[1] == "-de" # still the plain JAR-compatible drop-in call
|
|
|
|
|
|
def test_incompatible_flag_is_skipped_not_sent(tmp_path):
|
|
cmd = _route(
|
|
tmp_path,
|
|
ALL_FLAGS,
|
|
{"freeroute_engine": "room", "freeroute_diagonal": True},
|
|
)
|
|
assert "--diagonal" not in cmd # room does not implement it
|
|
|
|
|
|
def test_exact_engine_can_use_diagonal(tmp_path):
|
|
cmd = _route(
|
|
tmp_path,
|
|
ALL_FLAGS,
|
|
{
|
|
"freeroute_engine": "exact",
|
|
"freeroute_pack": False,
|
|
"freeroute_diagonal": True,
|
|
},
|
|
)
|
|
assert cmd[cmd.index("--engine") + 1] == "exact"
|
|
assert "--diagonal" in cmd
|
|
assert "--pack" not in cmd # exact does not implement packing
|