Support exact octagon tiles in the search tree clearance checks
Add optional exact/seg fields to TreeShape for a diagonal trace tile: tile holds the bounding box for broad-phase bucketing, exact holds the tight octagon copper, and seg=(a,b,half_width) rebuilds the clearance region on demand. Every orthogonal tile leaves both None and takes the identical box code path, so the box-only tree stays byte-for-byte unchanged (whole suite green). clearance_conflict now tests a stored diagonal tile with an exact 2-D overlap; clearance_conflict_shape checks a diagonal trace's clearance octagon against stored copper; has_violation runs the exact octagon test whenever either tile is diagonal and the plain box overlap otherwise. All exact-integer via Simplex intersection.
This commit is contained in:
parent
8380318d3c
commit
20dc5fd96a
@ -18,7 +18,7 @@ from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from freeroute.geometry import IntBox
|
||||
from freeroute.geometry import IntBox, Simplex, overlaps_2d, segment_octagon
|
||||
|
||||
__all__ = ["TreeShape", "ShapeSearchTree"]
|
||||
|
||||
@ -31,6 +31,13 @@ class TreeShape:
|
||||
board items (pads, keepouts). The routing DRC only counts violations that
|
||||
involve at least one routed tile — two source-design pads that are already
|
||||
closer than the clearance are a property of the input, not of the router.
|
||||
|
||||
``exact`` / ``seg`` are set only for a 45-degree (diagonal) trace tile: ``tile``
|
||||
is then the bounding box (broad-phase only), ``exact`` is the tight octagon
|
||||
copper :class:`~freeroute.geometry.Simplex`, and ``seg`` is ``(a, b,
|
||||
half_width)`` so the clearance region can be rebuilt exactly on demand. For
|
||||
every orthogonal tile both are ``None`` and all code paths are byte-identical
|
||||
to the box-only tree.
|
||||
"""
|
||||
|
||||
owner: int # a caller-chosen id grouping the tiles of one item/connection
|
||||
@ -38,10 +45,22 @@ class TreeShape:
|
||||
layers: frozenset[int]
|
||||
tile: IntBox
|
||||
routed: bool = False
|
||||
exact: Simplex | None = None
|
||||
seg: tuple | None = None # (a: IntPoint, b: IntPoint, half_width: int)
|
||||
|
||||
def on_layer(self, layer: int) -> bool:
|
||||
return layer in self.layers
|
||||
|
||||
def clearance_region(self, clearance: int):
|
||||
"""This tile's copper grown outward by ``clearance`` (a convex tile).
|
||||
|
||||
Orthogonal: the box offset by ``clearance``. Diagonal: the octagon of the
|
||||
segment swept by ``half_width + clearance`` (exact integer superset)."""
|
||||
if self.seg is None:
|
||||
return self.tile.offset(clearance)
|
||||
a, b, half_width = self.seg
|
||||
return segment_octagon(a, b, half_width + clearance)
|
||||
|
||||
|
||||
class ShapeSearchTree:
|
||||
"""Bounding-box spatial hash of :class:`TreeShape` with exact queries."""
|
||||
@ -110,7 +129,27 @@ class ShapeSearchTree:
|
||||
for shape in self.overlapping(expanded):
|
||||
if shape.net_no == net_no or not shape.on_layer(layer):
|
||||
continue
|
||||
if shape.tile.overlaps(expanded):
|
||||
if shape.exact is not None:
|
||||
if overlaps_2d(shape.exact, expanded):
|
||||
return True
|
||||
elif shape.tile.overlaps(expanded):
|
||||
return True
|
||||
return False
|
||||
|
||||
def clearance_conflict_shape(
|
||||
self, region, net_no: int, layer: int
|
||||
) -> bool:
|
||||
"""True if the convex ``region`` (a clearance-grown copper tile, e.g. a
|
||||
diagonal trace's octagon) overlaps a different net's copper on ``layer``.
|
||||
|
||||
``region`` already carries the clearance, so this tests a bare 2-D overlap
|
||||
against each candidate's exact tile (or its box). Used to check a diagonal
|
||||
trace whose growth is not an axis-aligned box."""
|
||||
for shape in self.overlapping(region.bounding_box()):
|
||||
if shape.net_no == net_no or not shape.on_layer(layer):
|
||||
continue
|
||||
other = shape.exact if shape.exact is not None else shape.tile
|
||||
if overlaps_2d(region, other):
|
||||
return True
|
||||
return False
|
||||
|
||||
@ -124,6 +163,7 @@ class ShapeSearchTree:
|
||||
"""
|
||||
for shape in self.all_shapes():
|
||||
expanded = shape.tile.offset(clearance)
|
||||
grown = None # exact clearance region, built lazily only for diagonals
|
||||
for other in self.overlapping(expanded):
|
||||
if other is shape or other.net_no == shape.net_no:
|
||||
continue
|
||||
@ -131,6 +171,13 @@ class ShapeSearchTree:
|
||||
continue # static input pair (e.g. pad vs pad) — not a routing DRC
|
||||
if not (shape.layers & other.layers):
|
||||
continue
|
||||
if other.tile.overlaps(expanded):
|
||||
if shape.exact is None and other.exact is None:
|
||||
if other.tile.overlaps(expanded): # byte-identical box path
|
||||
return shape
|
||||
continue
|
||||
if grown is None:
|
||||
grown = shape.clearance_region(clearance)
|
||||
other_tile = other.exact if other.exact is not None else other.tile
|
||||
if overlaps_2d(grown, other_tile):
|
||||
return shape
|
||||
return None
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user