[planner] early exit if rotation doesn't match
This commit is contained in:
parent
f864ebbeab
commit
84664303f1
2 changed files with 65 additions and 0 deletions
|
|
@ -857,6 +857,22 @@ class Solver:
|
|||
return False
|
||||
return self.request.out_ptype is None or ptypes_compatible(end_port.ptype, self.request.out_ptype)
|
||||
|
||||
def rotation_matches_request(self, steps: Sequence[SelectedPrimitive]) -> bool:
|
||||
"""
|
||||
Return true when a sequence can produce the requested output rotation.
|
||||
|
||||
Primitive parameters do not affect the rotation contract, so
|
||||
rotation-impossible candidates can be rejected before parameter solving.
|
||||
"""
|
||||
angle = 0.0
|
||||
for step in steps:
|
||||
step_rotation = step.out_port.rotation
|
||||
if step_rotation is None:
|
||||
raise BuildError('Primitive endpoints must have rotation')
|
||||
angle += float(step_rotation) + pi
|
||||
rotation_delta = ((angle - pi) - self.request.out_rotation) % (2 * pi)
|
||||
return is_close(rotation_delta, 0) or is_close(rotation_delta, 2 * pi)
|
||||
|
||||
def finalize(self, steps: Sequence[SelectedPrimitive]) -> Candidate:
|
||||
"""
|
||||
Try all small solve sets for one raw sequence and return the first match.
|
||||
|
|
@ -874,6 +890,8 @@ class Solver:
|
|||
raise BuildError(f'{self.request.route_name} route requires a jog constraint')
|
||||
constraints.append(('y', float(self.request.jog)))
|
||||
route_constraints = tuple(constraints)
|
||||
if not self.rotation_matches_request(steps):
|
||||
raise BuildError(f'{self.request.route_name} composed primitive route is unsupported')
|
||||
adjustable = self.adjustable_indices(steps)
|
||||
solve_sets: list[tuple[int, ...]] = [()]
|
||||
max_solve = min(len(route_constraints), len(adjustable))
|
||||
|
|
|
|||
|
|
@ -422,6 +422,53 @@ def test_pather_selects_lowest_cost_offer() -> None:
|
|||
assert numpy.allclose(p.ports['A'].offset, (-7, 0))
|
||||
|
||||
|
||||
def test_solver_rejects_rotation_impossible_candidates_before_parameter_solving() -> None:
|
||||
invalid_parameters: list[float] = []
|
||||
|
||||
class RotationOfferTool(PlanningOnlyTool):
|
||||
def primitive_offers(
|
||||
self,
|
||||
kind: Literal['straight', 'bend', 's', 'u'],
|
||||
*,
|
||||
in_ptype: str | None = None,
|
||||
out_ptype: str | None = None,
|
||||
**kwargs: Any,
|
||||
) -> tuple[PrimitiveOffer, ...]:
|
||||
_ = kwargs
|
||||
if kind != 'straight':
|
||||
return ()
|
||||
|
||||
def invalid_endpoint(length: float) -> Port:
|
||||
invalid_parameters.append(length)
|
||||
return Port((length, 0), rotation=0, ptype=out_ptype or in_ptype)
|
||||
|
||||
def valid_endpoint(length: float) -> Port:
|
||||
return Port((length, 0), rotation=pi, ptype=out_ptype or in_ptype)
|
||||
|
||||
return (
|
||||
StraightOffer(
|
||||
in_ptype=in_ptype,
|
||||
out_ptype=out_ptype,
|
||||
endpoint_planner=invalid_endpoint,
|
||||
commit_planner=lambda length: {'kind': 'invalid', 'length': length},
|
||||
),
|
||||
StraightOffer(
|
||||
in_ptype=in_ptype,
|
||||
out_ptype=out_ptype,
|
||||
endpoint_planner=valid_endpoint,
|
||||
commit_planner=lambda length: {'kind': 'valid', 'length': length},
|
||||
),
|
||||
)
|
||||
|
||||
p = Pather(Library(), tools=RotationOfferTool(), auto_render=False)
|
||||
p.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
||||
|
||||
p.straight('A', 7)
|
||||
|
||||
assert p._paths['A'][0].data == {'kind': 'valid', 'length': 7}
|
||||
assert invalid_parameters == [0.0, 0.0]
|
||||
|
||||
|
||||
def test_pather_commits_only_selected_offer() -> None:
|
||||
committed: list[str] = []
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue