freeroute ignored the DSN's planes (KiCad's zones), so it never knew that pads in a pour are joined by it. On the rc4 power stage those pads sit inside "keep router off" areas as well, and every connection to them was dropped without a word. - Read (plane <net> <shape>) and put each zone on the board as a ConductionArea of its net. Zones are not obstacles: KiCad refills them around other nets' copper. - The grid groups each net's pads and zone pieces that copper already joins (a pad in a zone, two zones sharing a pad) and routes only between groups. A connection may end on a zone by a via inside its copper, or by a track where tracks are allowed. - wire_keepout now blocks traces only, as KiCad's "no tracks" rule area does; via_keepout only vias; keepout both, in the grid and in the exact verifier. rc4 power stage, exact engine, resolution 2: 2 unconnected, down from 8 (FreeRouting JAR: 12), no DRC errors.
230 lines
5.9 KiB
Python
230 lines
5.9 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",
|
|
"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 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)
|
|
#: 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
|