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