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.
255 lines
6.5 KiB
Python
255 lines
6.5 KiB
Python
"""Typed board model produced by parsing a Specctra DSN file.
|
|
|
|
These dataclasses are the Python analogue of the data that FreeRouting's
|
|
``io/specctra/parser`` scope readers accumulate before constructing a
|
|
``RoutingBoard``. They stay at the DSN level (names, raw coordinates in DSN
|
|
units) — turning them into board geometry is a later port stage.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
|
|
from .shapes import Area, Shape
|
|
|
|
__all__ = [
|
|
"ParserInfo",
|
|
"Resolution",
|
|
"Layer",
|
|
"WidthRule",
|
|
"ClearanceRule",
|
|
"StructureRules",
|
|
"Padstack",
|
|
"Pin",
|
|
"Image",
|
|
"ComponentPlace",
|
|
"ComponentPlacement",
|
|
"NetPin",
|
|
"Net",
|
|
"NetClass",
|
|
"WiringVia",
|
|
"WiringWire",
|
|
"Keepout",
|
|
"DsnBoard",
|
|
]
|
|
|
|
|
|
@dataclass
|
|
class ParserInfo:
|
|
"""The ``(parser ...)`` scope."""
|
|
|
|
string_quote: str = '"'
|
|
host_cad: str | None = None
|
|
host_version: str | None = None
|
|
constants: list[tuple[str, str]] = field(default_factory=list)
|
|
generated_by_freerouting: bool = False
|
|
|
|
|
|
@dataclass
|
|
class Resolution:
|
|
"""The ``(resolution <unit> <value>)`` scope. Defaults match FreeRouting."""
|
|
|
|
unit: str = "mil"
|
|
value: int = 100
|
|
|
|
|
|
@dataclass
|
|
class Layer:
|
|
"""A ``(layer ...)`` entry. ``index`` is the physical layer number, 0 = top."""
|
|
|
|
name: str
|
|
index: int
|
|
is_signal: bool = True
|
|
net_names: list[str] = field(default_factory=list)
|
|
|
|
|
|
@dataclass
|
|
class WidthRule:
|
|
value: float
|
|
|
|
|
|
@dataclass
|
|
class ClearanceRule:
|
|
value: float
|
|
#: class-pair names from ``(type a-b)`` / ``(type smd_smd)``; empty = default
|
|
class_pairs: list[str] = field(default_factory=list)
|
|
|
|
|
|
@dataclass
|
|
class StructureRules:
|
|
"""The default ``(rule ...)`` block plus control/snap settings."""
|
|
|
|
width_rules: list[WidthRule] = field(default_factory=list)
|
|
clearance_rules: list[ClearanceRule] = field(default_factory=list)
|
|
|
|
|
|
@dataclass
|
|
class Padstack:
|
|
"""A ``(padstack ...)`` — one or more per-layer pad shapes plus flags."""
|
|
|
|
name: str
|
|
shapes: list[Shape] = field(default_factory=list)
|
|
attach_allowed: bool = True
|
|
placed_absolute: bool = False
|
|
|
|
|
|
@dataclass
|
|
class Pin:
|
|
"""A pin within an image: ``(pin <padstack> [ (rotate d) ] <name> x y)``."""
|
|
|
|
padstack_name: str
|
|
name: str
|
|
x: float
|
|
y: float
|
|
rotation: float = 0.0
|
|
|
|
|
|
@dataclass
|
|
class Image:
|
|
"""A ``(image ...)`` — a component footprint definition."""
|
|
|
|
name: str
|
|
is_front: bool = True
|
|
pins: list[Pin] = field(default_factory=list)
|
|
outlines: list[Shape] = field(default_factory=list)
|
|
keepouts: list[Area] = field(default_factory=list)
|
|
via_keepouts: list[Area] = field(default_factory=list)
|
|
wire_keepouts: list[Area] = field(default_factory=list)
|
|
place_keepouts: list[Area] = field(default_factory=list)
|
|
|
|
|
|
@dataclass
|
|
class ComponentPlace:
|
|
"""One placed instance: ``(place <refdes> x y front|back rot ...)``.
|
|
|
|
``x`` / ``y`` are ``None`` when the component is declared but not yet placed.
|
|
"""
|
|
|
|
name: str
|
|
x: float | None = None
|
|
y: float | None = None
|
|
is_front: bool = True
|
|
rotation: float = 0.0
|
|
position_fixed: bool = False
|
|
part_number: str | None = None
|
|
|
|
|
|
@dataclass
|
|
class ComponentPlacement:
|
|
"""A ``(component <libname> (place ...) ...)`` group."""
|
|
|
|
lib_name: str
|
|
places: list[ComponentPlace] = field(default_factory=list)
|
|
|
|
|
|
@dataclass
|
|
class NetPin:
|
|
"""A ``Comp-Pin`` reference inside a net's ``(pins ...)`` list."""
|
|
|
|
component: str
|
|
pin: str
|
|
|
|
|
|
@dataclass
|
|
class Net:
|
|
"""A ``(net <name> [subnet] (pins ...))`` entry."""
|
|
|
|
name: str
|
|
subnet: int = 1
|
|
pins: list[NetPin] = field(default_factory=list)
|
|
|
|
|
|
@dataclass
|
|
class NetClass:
|
|
"""A ``(class <name> net... (circuit (use_via ...)) (rule ...))`` entry."""
|
|
|
|
name: str
|
|
net_names: list[str] = field(default_factory=list)
|
|
width_rules: list[WidthRule] = field(default_factory=list)
|
|
clearance_rules: list[ClearanceRule] = field(default_factory=list)
|
|
use_via: list[str] = field(default_factory=list)
|
|
use_layer: list[str] = field(default_factory=list)
|
|
via_rule: str | None = None
|
|
|
|
|
|
@dataclass
|
|
class Plane:
|
|
"""A ``(plane <net> <shape>)``: a copper zone of that net (KiCad's zones)."""
|
|
|
|
net: str
|
|
shape: Shape
|
|
|
|
|
|
@dataclass
|
|
class Keepout:
|
|
"""A structure-level keepout area with its kind."""
|
|
|
|
area: Area
|
|
kind: str = "keepout" # keepout | via_keepout | wire_keepout | place_keepout
|
|
|
|
|
|
@dataclass
|
|
class WiringWire:
|
|
"""A track already on the board: ``(wire (path LAYER WIDTH x y ...) (net N))``."""
|
|
|
|
net: str
|
|
layer: str
|
|
width: float
|
|
coords: list[float] = field(default_factory=list)
|
|
|
|
|
|
@dataclass
|
|
class WiringVia:
|
|
"""A via already on the board: ``(via PADSTACK x y (net N))``."""
|
|
|
|
net: str
|
|
padstack: str
|
|
x: float
|
|
y: float
|
|
|
|
|
|
@dataclass
|
|
class DsnBoard:
|
|
"""The fully parsed DSN file."""
|
|
|
|
name: str = ""
|
|
parser: ParserInfo = field(default_factory=ParserInfo)
|
|
resolution: Resolution = field(default_factory=Resolution)
|
|
unit: str = "mil"
|
|
layers: list[Layer] = field(default_factory=list)
|
|
#: copper zones, as planes of a net
|
|
planes: list[Plane] = field(default_factory=list)
|
|
#: copper already routed (the ``wiring`` scope), kept fixed
|
|
wires: list[WiringWire] = field(default_factory=list)
|
|
vias: list[WiringVia] = field(default_factory=list)
|
|
#: bounding-box boundary shape (layer ``pcb``), if present
|
|
boundary: Shape | None = None
|
|
#: outline shapes (layer ``signal``)
|
|
outlines: list[Shape] = field(default_factory=list)
|
|
via_padstack_names: list[str] = field(default_factory=list)
|
|
structure_rules: StructureRules = field(default_factory=StructureRules)
|
|
snap_angle: str = "fortyfive_degree"
|
|
via_at_smd_allowed: bool = False
|
|
keepouts: list[Keepout] = field(default_factory=list)
|
|
padstacks: list[Padstack] = field(default_factory=list)
|
|
images: list[Image] = field(default_factory=list)
|
|
placements: list[ComponentPlacement] = field(default_factory=list)
|
|
nets: list[Net] = field(default_factory=list)
|
|
net_classes: list[NetClass] = field(default_factory=list)
|
|
|
|
# Convenience lookups -----------------------------------------------------
|
|
def layer_names(self) -> list[str]:
|
|
return [layer.name for layer in self.layers]
|
|
|
|
def net(self, name: str) -> Net | None:
|
|
for n in self.nets:
|
|
if n.name == name:
|
|
return n
|
|
return None
|
|
|
|
def padstack(self, name: str) -> Padstack | None:
|
|
for p in self.padstacks:
|
|
if p.name == name:
|
|
return p
|
|
return None
|