[Pather / planner / ports] improve trace_into connection accuracy

This commit is contained in:
Jan Petykiewicz 2026-07-16 08:35:46 -07:00
commit 2b6782c93e
4 changed files with 35 additions and 8 deletions

View file

@ -32,7 +32,6 @@ from __future__ import annotations
from collections.abc import Iterable, Mapping, Sequence from collections.abc import Iterable, Mapping, Sequence
from dataclasses import dataclass, replace from dataclasses import dataclass, replace
from itertools import combinations from itertools import combinations
from math import cos, sin
from typing import Any, Literal from typing import Any, Literal
import numpy import numpy
@ -41,7 +40,7 @@ from numpy.typing import ArrayLike
from ...error import BuildError, PortError from ...error import BuildError, PortError
from ...ports import Port from ...ports import Port
from ...utils import PTypeMatch, SupportsBool, ptype_match, ptypes_compatible from ...utils import PTypeMatch, SupportsBool, ptype_match, ptypes_compatible, rotation_matrix_2d
from ..tools import ( from ..tools import (
BendOffer, BendOffer,
PrimitiveKind, PrimitiveKind,
@ -579,10 +578,11 @@ class Solver:
out_port = step.out_port out_port = step.out_port
if out_port.rotation is None: if out_port.rotation is None:
raise ToolContractError('Primitive endpoints must have rotation') raise ToolContractError('Primitive endpoints must have rotation')
angle_cos = cos(angle) rotation = rotation_matrix_2d(angle)
angle_sin = sin(angle) out_x = float(out_port.x)
x += angle_cos * float(out_port.x) - angle_sin * float(out_port.y) out_y = float(out_port.y)
y += angle_sin * float(out_port.x) + angle_cos * float(out_port.y) x += rotation[0, 0] * out_x + rotation[0, 1] * out_y
y += rotation[1, 0] * out_x + rotation[1, 1] * out_y
angle += out_port.rotation + pi angle += out_port.rotation + pi
ptype = out_port.ptype ptype = out_port.ptype
return Port((x, y), rotation=angle - pi, ptype=ptype) return Port((x, y), rotation=angle - pi, ptype=ptype)

View file

@ -574,10 +574,10 @@ class PortList(metaclass=ABCMeta):
raise PortError(msg) raise PortError(msg)
translations = a_offsets - b_offsets translations = a_offsets - b_offsets
if not numpy.allclose(translations, 0): if not numpy.allclose(a_offsets, b_offsets):
msg = 'Port translations do not match:\n' msg = 'Port translations do not match:\n'
for nn, (kk, vv) in enumerate(connections.items()): for nn, (kk, vv) in enumerate(connections.items()):
if not numpy.allclose(translations[nn], 0): if not numpy.allclose(a_offsets[nn], b_offsets[nn]):
msg += f'{kk} | {translations[nn]} | {vv}\n' msg += f'{kk} | {translations[nn]} | {vv}\n'
raise PortError(msg) raise PortError(msg)

View file

@ -113,6 +113,22 @@ def test_pather_trace_into_shapes() -> None:
assert numpy.isclose(p.pattern.ports['I'].rotation, pi / 2) assert numpy.isclose(p.pattern.ports['I'].rotation, pi / 2)
def test_pather_trace_into_large_composed_manhattan_route_plugs() -> None:
p = Pather(
Library(),
tools=PathTool(layer='M1', width=1000, ptype='wire'),
render='deferred',
)
p.ports['src'] = Port((123.25, 456.75), rotation=0, ptype='wire')
p.ports['dst'] = Port((-99_999_876.75, 20_000_456.75), rotation=0, ptype='wire')
p.trace_into('src', 'dst')
assert 'src' not in p.ports
assert 'dst' not in p.ports
assert [step.kind for step in p._paths['src']] == ['straight', 'bend', 'straight', 'bend', 'plug']
def test_pather_trace_into_refines_output_adapter_route_before_plug() -> None: def test_pather_trace_into_refines_output_adapter_route_before_plug() -> None:
class TransitionTool(Tool): class TransitionTool(Tool):
def primitive_offers( def primitive_offers(

View file

@ -169,6 +169,17 @@ def test_port_list_plugged() -> None:
assert not pl.ports # Both should be removed assert not pl.ports # Both should be removed
def test_port_list_plugged_uses_coordinate_relative_tolerance() -> None:
pattern = Pattern(ports={
"A": Port((10, 10), 0),
"B": Port((10 + 5e-8, 10), pi),
})
pattern.plugged({"A": "B"})
assert not pattern.ports
def test_port_list_plugged_ptype_compatibility_warnings(caplog: pytest.LogCaptureFixture) -> None: def test_port_list_plugged_ptype_compatibility_warnings(caplog: pytest.LogCaptureFixture) -> None:
caplog.set_level("WARNING", logger="masque.ports") caplog.set_level("WARNING", logger="masque.ports")