A route may not leave a cell inside a wire keepout as a wire, nor reach a pin there by one: it changes layer instead, and the via goes in the pad. A pin whose every stub enters a keepout is reached by a via only. A structure keepout named rule_area... (mckicad names KiCad rule areas so) is checked for overlap only, as KiCad's DRC does, with the free ends of each wire tested for their round caps.
99 lines
4.1 KiB
Python
99 lines
4.1 KiB
Python
"""Tests for the exact ShapeSearchTree spatial index."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from freeroute.board import ShapeSearchTree, TreeShape
|
|
from freeroute.geometry import IntBox
|
|
|
|
|
|
def shape(owner, net, layer, box, routed=False):
|
|
return TreeShape(owner, net, frozenset({layer}), box, routed=routed)
|
|
|
|
|
|
def test_insert_and_overlapping_exact():
|
|
tree = ShapeSearchTree(bucket_size=100)
|
|
tree.insert(shape(0, 1, 0, IntBox(0, 0, 50, 50)))
|
|
tree.insert(shape(1, 2, 0, IntBox(200, 200, 250, 250)))
|
|
# a query box overlapping only the first shape
|
|
hit = tree.overlapping(IntBox(40, 40, 60, 60))
|
|
assert [s.owner for s in hit] == [0]
|
|
# a query far from everything
|
|
assert tree.overlapping(IntBox(1000, 1000, 1010, 1010)) == []
|
|
|
|
|
|
def test_touching_boxes_do_not_overlap_but_do_intersect():
|
|
tree = ShapeSearchTree(bucket_size=100)
|
|
tree.insert(shape(0, 1, 0, IntBox(0, 0, 10, 10)))
|
|
# shares the edge x=10 -> intersection is 1-D (found by broad phase)
|
|
assert len(tree.overlapping(IntBox(10, 0, 20, 10))) == 1
|
|
|
|
|
|
def test_remove_owner():
|
|
tree = ShapeSearchTree(bucket_size=100)
|
|
tree.insert(shape(0, 1, 0, IntBox(0, 0, 50, 50)))
|
|
tree.insert(shape(0, 1, 0, IntBox(60, 0, 90, 50))) # same owner, 2 tiles
|
|
tree.insert(shape(1, 2, 0, IntBox(0, 0, 50, 50)))
|
|
tree.remove_owner(0)
|
|
assert all(s.owner == 1 for s in tree.all_shapes())
|
|
assert len(tree.all_shapes()) == 1
|
|
|
|
|
|
def test_clearance_conflict_respects_the_clearance_gap():
|
|
tree = ShapeSearchTree(bucket_size=1000)
|
|
tree.insert(shape(0, 1, 0, IntBox(0, 0, 100, 100), routed=True))
|
|
# a net-2 box exactly `clearance` (20) to the right -> allowed (touching)
|
|
ok = IntBox(120, 0, 200, 100)
|
|
assert not tree.clearance_conflict(ok, net_no=2, layer=0, clearance=20)
|
|
# one unit closer -> conflict
|
|
bad = IntBox(119, 0, 200, 100)
|
|
assert tree.clearance_conflict(bad, net_no=2, layer=0, clearance=20)
|
|
# same net never conflicts
|
|
assert not tree.clearance_conflict(bad, net_no=1, layer=0, clearance=20)
|
|
# different layer never conflicts
|
|
assert not tree.clearance_conflict(bad, net_no=2, layer=1, clearance=20)
|
|
|
|
|
|
def test_has_violation_ignores_static_pad_pairs():
|
|
tree = ShapeSearchTree(bucket_size=1000)
|
|
# two different-net *static* pads closer than clearance -> not a routing DRC
|
|
tree.insert(shape(0, 1, 0, IntBox(0, 0, 10, 10), routed=False))
|
|
tree.insert(shape(1, 2, 0, IntBox(11, 0, 20, 10), routed=False))
|
|
assert tree.has_violation(clearance=20) is None
|
|
# a routed trace of net 3 within clearance of pad net 1 -> violation
|
|
tree.insert(shape(2, 3, 0, IntBox(11, 0, 20, 10), routed=True))
|
|
assert tree.has_violation(clearance=20) is not None
|
|
|
|
|
|
def test_a_rule_area_conflicts_only_on_overlap():
|
|
tree = ShapeSearchTree()
|
|
tree.insert(TreeShape(1, -1, frozenset({0}), IntBox(100, 0, 200, 100), clearance=False))
|
|
near = IntBox(0, 0, 95, 100) # 5 short of the area, well inside clearance 50
|
|
over = IntBox(0, 0, 105, 100)
|
|
assert not tree.clearance_conflict(near, 7, 0, 50)
|
|
assert tree.clearance_conflict(over, 7, 0, 50)
|
|
|
|
|
|
def test_an_ordinary_keepout_still_needs_clearance():
|
|
tree = ShapeSearchTree()
|
|
tree.insert(TreeShape(1, -1, frozenset({0}), IntBox(100, 0, 200, 100)))
|
|
assert tree.clearance_conflict(IntBox(0, 0, 95, 100), 7, 0, 50)
|
|
|
|
|
|
def test_the_routed_board_check_treats_a_rule_area_the_same_way():
|
|
tree = ShapeSearchTree()
|
|
tree.insert(TreeShape(1, -1, frozenset({0}), IntBox(100, 0, 200, 100), clearance=False))
|
|
tree.insert(TreeShape(2, 7, frozenset({0}), IntBox(0, 0, 95, 100), routed=True))
|
|
assert tree.has_violation(50) is None
|
|
tree.insert(TreeShape(3, 7, frozenset({0}), IntBox(150, 0, 160, 100), routed=True))
|
|
assert tree.has_violation(50) is not None
|
|
|
|
|
|
|
|
def test_rule_area_overlap_sees_only_rule_areas():
|
|
tree = ShapeSearchTree()
|
|
tree.insert(TreeShape(1, -1, frozenset({0}), IntBox(100, 0, 200, 100), clearance=False))
|
|
tree.insert(TreeShape(2, -1, frozenset({0}), IntBox(300, 0, 400, 100)))
|
|
assert tree.rule_area_overlap(IntBox(90, 40, 110, 60), 0)
|
|
assert not tree.rule_area_overlap(IntBox(290, 40, 310, 60), 0)
|
|
assert not tree.rule_area_overlap(IntBox(90, 40, 110, 60), 1)
|