[Pather] add strategy arg
This commit is contained in:
parent
0230fb49a6
commit
cf3f72b828
5 changed files with 188 additions and 29 deletions
|
|
@ -6,6 +6,7 @@ import pytest
|
|||
from numpy import pi
|
||||
|
||||
from masque import Library, Path, Port, Pather
|
||||
from masque.builder.planner import RoutingPlanner
|
||||
from masque.builder.tools import (
|
||||
BendOffer,
|
||||
PathTool,
|
||||
|
|
@ -422,6 +423,103 @@ def test_pather_selects_lowest_cost_offer() -> None:
|
|||
assert numpy.allclose(p.ports['A'].offset, (-7, 0))
|
||||
|
||||
|
||||
class StrategyTieTool(PlanningOnlyTool):
|
||||
def __init__(self) -> None:
|
||||
self.seen_kwargs: list[dict[str, Any]] = []
|
||||
|
||||
def primitive_offers(
|
||||
self,
|
||||
kind: Literal['straight', 'bend', 's', 'u'],
|
||||
*,
|
||||
in_ptype: str | None = None,
|
||||
out_ptype: str | None = None,
|
||||
**kwargs: Any,
|
||||
) -> tuple[PrimitiveOffer, ...]:
|
||||
self.seen_kwargs.append(dict(kwargs))
|
||||
endpoint_ptype = out_ptype or in_ptype
|
||||
if kind == 'straight':
|
||||
return (StraightOffer(
|
||||
in_ptype=in_ptype,
|
||||
out_ptype=endpoint_ptype,
|
||||
**offer_callbacks(lambda length: (
|
||||
Port((length, 0), rotation=pi, ptype=endpoint_ptype),
|
||||
{'kind': 'straight', 'length': length},
|
||||
)),
|
||||
),)
|
||||
if kind == 's':
|
||||
return (SOffer(
|
||||
in_ptype=in_ptype,
|
||||
out_ptype=endpoint_ptype,
|
||||
**offer_callbacks(lambda jog: (
|
||||
Port((3, jog), rotation=pi, ptype=endpoint_ptype),
|
||||
{'kind': 's', 'jog': jog},
|
||||
)),
|
||||
),)
|
||||
return ()
|
||||
|
||||
|
||||
def pather_with_strategy_tool(
|
||||
planner: RoutingPlanner | None = None,
|
||||
) -> tuple[Pather, StrategyTieTool]:
|
||||
tool = StrategyTieTool()
|
||||
pather = Pather(Library(), tools=tool, planner=planner, render='deferred')
|
||||
pather.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
||||
return pather, tool
|
||||
|
||||
|
||||
def test_pather_route_strategy_defaults_to_straight_first() -> None:
|
||||
pather, _tool = pather_with_strategy_tool()
|
||||
|
||||
pather.jog('A', 4, length=10)
|
||||
|
||||
assert [step.data['kind'] for step in pather._paths['A']] == ['straight', 's']
|
||||
assert pather._paths['A'][0].data['length'] == 7
|
||||
|
||||
|
||||
def test_pather_route_strategy_uses_planner_default() -> None:
|
||||
pather, _tool = pather_with_strategy_tool(RoutingPlanner(strategy='turn_first'))
|
||||
|
||||
pather.jog('A', 4, length=10)
|
||||
|
||||
assert [step.data['kind'] for step in pather._paths['A']] == ['s', 'straight']
|
||||
assert pather._paths['A'][1].data['length'] == 7
|
||||
|
||||
|
||||
def test_pather_route_strategy_per_route_overrides_planner_default() -> None:
|
||||
pather, _tool = pather_with_strategy_tool(RoutingPlanner(strategy='turn_first'))
|
||||
|
||||
pather.jog('A', 4, length=10, strategy='straight_first')
|
||||
|
||||
assert [step.data['kind'] for step in pather._paths['A']] == ['straight', 's']
|
||||
|
||||
|
||||
def test_pather_route_strategy_per_route_can_request_turn_first() -> None:
|
||||
pather, _tool = pather_with_strategy_tool()
|
||||
|
||||
pather.jog('A', 4, length=10, strategy='turn_first')
|
||||
|
||||
assert [step.data['kind'] for step in pather._paths['A']] == ['s', 'straight']
|
||||
|
||||
|
||||
def test_pather_route_strategy_is_not_forwarded_to_tool() -> None:
|
||||
pather, tool = pather_with_strategy_tool()
|
||||
|
||||
pather.jog('A', 4, length=10, strategy='turn_first', marker='sentinel')
|
||||
|
||||
assert tool.seen_kwargs
|
||||
assert all('strategy' not in kwargs for kwargs in tool.seen_kwargs)
|
||||
assert any(kwargs.get('marker') == 'sentinel' for kwargs in tool.seen_kwargs)
|
||||
|
||||
|
||||
def test_pather_route_strategy_rejects_invalid_values() -> None:
|
||||
with pytest.raises(BuildError, match='Invalid route strategy'):
|
||||
RoutingPlanner(strategy='sideways')
|
||||
|
||||
pather, _tool = pather_with_strategy_tool()
|
||||
with pytest.raises(BuildError, match='Invalid route strategy'):
|
||||
pather.jog('A', 4, length=10, strategy='sideways')
|
||||
|
||||
|
||||
def test_solver_rejects_rotation_impossible_candidates_before_parameter_solving() -> None:
|
||||
invalid_parameters: list[float] = []
|
||||
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ def test_deferred_render_jog_uses_lowest_cost_two_bend_route(deferred_render_set
|
|||
|
||||
rp.render()
|
||||
path_shape = cast("Path", rp.pattern.shapes[(1, 0)][0])
|
||||
assert_allclose(path_shape.vertices, [[0, 0], [0, -1], [1, -1], [3, -1], [4, -1], [4, -2], [4, -10]], atol=1e-10)
|
||||
assert_allclose(path_shape.vertices, [[0, 0], [0, -8], [0, -9], [1, -9], [3, -9], [4, -9], [4, -10]], atol=1e-10)
|
||||
assert_allclose(rp.ports["start"].offset, [4, -10], atol=1e-10)
|
||||
|
||||
def test_deferred_render_mirror_preserves_planned_bend_geometry(deferred_render_setup: tuple[Pather, PathTool, Library]) -> None:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue