diff --git a/src/rpmroute/route/smooth.py b/src/rpmroute/route/smooth.py index 4c0d3f0..b377378 100644 --- a/src/rpmroute/route/smooth.py +++ b/src/rpmroute/route/smooth.py @@ -39,6 +39,9 @@ from .pipeline import _dsn_units_per_um, _mm_to_board, _rule_clearance_dsn, _via #: how many points ahead a staircase collapse looks from each point LOOKAHEAD = 64 +#: a 45-degree leg at least this many track widths long: a shorter one is a +#: kink beside its end's pad, where a refilled pour leaves a copper sliver +MIN_DIAGONAL_WIDTHS = 2 @dataclass @@ -121,6 +124,13 @@ def _candidates(a: IntPoint, b: IntPoint) -> list[list[IntPoint]]: ] +def _too_short(a: IntPoint, b: IntPoint, half: int) -> bool: + """A slanted leg shorter than MIN_DIAGONAL_WIDTHS track widths.""" + if a.x == b.x or a.y == b.y: + return False + return (b.x - a.x) ** 2 + (b.y - a.y) ** 2 < (MIN_DIAGONAL_WIDTHS * 2 * half) ** 2 + + def _dedupe(pts: list[IntPoint]) -> list[IntPoint]: out: list[IntPoint] = [] for p in pts: @@ -211,6 +221,8 @@ class _Smoother: def path_clear(self, path: list[IntPoint], half: int, net: int, layer: int) -> bool: """New legs and new corners of ``path`` (its ends are kept points).""" + if any(_too_short(p, q, half) for p, q in zip(path, path[1:], strict=False)): + return False return all( self.leg_clear(p, q, half, net, layer) for p, q in zip(path, path[1:], strict=False) ) and all(self.corner_clear(c, half, net, layer) for c in path[1:-1]) @@ -262,7 +274,8 @@ class _Smoother: cap_out = len_out if (b.x, b.y) in keep or i == len(pts) - 2 else len_out // 2 d = min(cap_in, cap_out) done = False - while d >= 2 * half: + # the chamfer's leg is d * sqrt(2) long + while d * d * 2 >= (MIN_DIAGONAL_WIDTHS * 2 * half) ** 2: p = IntPoint(c.x - ux * d, c.y - uy * d) q = IntPoint(c.x + vx * d, c.y + vy * d) corners = [x for x in (p, q) if (x.x, x.y) not in ((a.x, a.y), (b.x, b.y))] diff --git a/tests/route/test_smooth.py b/tests/route/test_smooth.py index 9b97f13..9b5f3ce 100644 --- a/tests/route/test_smooth.py +++ b/tests/route/test_smooth.py @@ -146,3 +146,17 @@ def test_a_detour_with_nothing_in_its_way_is_straightened(): assert _points(result.nets[0].wires[0]) == [(22000, -2000), (22000, -22000)] assert (report.before.right_angles, report.after.right_angles) == (2, 0) assert verify_result(dsn, result).clean + + +def test_a_jog_too_small_for_a_long_diagonal_stays_square(): + # a 0.1 mm jog halfway: its 45-degree leg would be shorter than two track + # widths, a kink beside the copper that leaves pour slivers + dsn = parse_dsn(_board(extra_places="(place A3 2100 -22000 front 0)").replace( + "(pins A1-1 A2-1)", "(pins A1-1 A3-1)")) + wire = [(2000, -2000), (2000, -12000), (2100, -12000), (2100, -22000)] + result = _result(_wire(wire)) + smooth(dsn, result) + pts = _points(result.nets[0].wires[0]) + for a, b in zip(pts, pts[1:], strict=False): + if a[0] != b[0] and a[1] != b[1]: + assert (b[0] - a[0]) ** 2 + (b[1] - a[1]) ** 2 >= (2 * 200) ** 2