Tracks and vias already on the board were ignored, so other nets could be routed straight through them (the flight controller's 82 GND stitching vias and its guard tracks). They are now copper of their net: other nets keep a pad's distance from them in the grid, the exact check and the room engine, and they add no connections to route.
102 lines
3.8 KiB
Python
102 lines
3.8 KiB
Python
"""``BasicBoard`` — the board container.
|
|
|
|
Ports the data-model core of ``board/BasicBoard.java``: the layer stack, the
|
|
net list, the clearance matrix, the bounding box, and the list of items with
|
|
insertion and query helpers. The routing operations live in ``RoutingBoard``
|
|
(the router phase); this class is what DSN import produces and what the
|
|
board-model invariants check.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from freeroute.geometry import IntBox
|
|
|
|
from .clearance import ClearanceMatrix
|
|
from .items import ConductionArea, Item, ObstacleArea, Pin, Trace, Via
|
|
from .layer import LayerStructure
|
|
from .net import Nets
|
|
|
|
__all__ = ["BasicBoard"]
|
|
|
|
|
|
class BasicBoard:
|
|
"""A board: layers, nets, clearances, a bounding box, and items."""
|
|
|
|
__slots__ = ("layer_structure", "nets", "clearance_matrix", "bounding_box", "outline", "_items")
|
|
|
|
def __init__(
|
|
self,
|
|
layer_structure: LayerStructure,
|
|
nets: Nets,
|
|
clearance_matrix: ClearanceMatrix,
|
|
bounding_box: IntBox,
|
|
) -> None:
|
|
self.layer_structure = layer_structure
|
|
self.nets = nets
|
|
self.clearance_matrix = clearance_matrix
|
|
self.bounding_box = bounding_box
|
|
#: the real board outline, when the DSN has a polygon boundary
|
|
self.outline = None
|
|
self._items: list[Item] = []
|
|
|
|
# --- layers -------------------------------------------------------------
|
|
|
|
def get_layer_count(self) -> int:
|
|
return len(self.layer_structure)
|
|
|
|
# --- items --------------------------------------------------------------
|
|
|
|
def insert(self, item: Item) -> Item:
|
|
self._items.append(item)
|
|
return item
|
|
|
|
def get_items(self) -> list[Item]:
|
|
return list(self._items)
|
|
|
|
def get_pins(self) -> list[Pin]:
|
|
return [it for it in self._items if isinstance(it, Pin)]
|
|
|
|
def get_obstacle_areas(self) -> list[ObstacleArea]:
|
|
"""Keepouts. Copper zones are not obstacles; see get_conduction_areas."""
|
|
return [
|
|
it
|
|
for it in self._items
|
|
if isinstance(it, ObstacleArea) and not isinstance(it, ConductionArea)
|
|
]
|
|
|
|
def get_conduction_areas(self) -> list[ConductionArea]:
|
|
return [it for it in self._items if isinstance(it, ConductionArea)]
|
|
|
|
def get_traces(self) -> list[Trace]:
|
|
return [it for it in self._items if isinstance(it, Trace)]
|
|
|
|
def get_fixed_copper(self) -> list[tuple[int, frozenset[int], IntBox]]:
|
|
"""Copper already routed on the board (the DSN's ``wiring``), as
|
|
``(net, layers, box)``. A track is its segments, its corners and the
|
|
round caps at its ends (as squares); a via is its largest pad."""
|
|
from freeroute.geometry.polyline import Polyline, PolylineShape, corner_box
|
|
|
|
out: list[tuple[int, frozenset[int], IntBox]] = []
|
|
for item in self._items:
|
|
if not item.fixed or not item.net_nos:
|
|
continue
|
|
net = item.net_nos[0]
|
|
if isinstance(item, Trace) and not item.is_empty():
|
|
layers = frozenset({item.layer})
|
|
for box in PolylineShape(Polyline(item.corners), item.half_width).tiles():
|
|
out.append((net, layers, box))
|
|
for end in (item.corners[0], item.corners[-1]):
|
|
out.append((net, layers, corner_box(end, item.half_width)))
|
|
elif isinstance(item, Via) and item.shape is not None:
|
|
out.append((net, frozenset(item.layers), item.shape.bounding_box()))
|
|
return out
|
|
|
|
def get_vias(self) -> list[Via]:
|
|
return [it for it in self._items if isinstance(it, Via)]
|
|
|
|
def get_connectable_items(self, net_no: int) -> list[Item]:
|
|
return [it for it in self._items if it.contains_net(net_no)]
|
|
|
|
def item_count(self) -> int:
|
|
return len(self._items)
|