"""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)