"""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 )`` 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 [ (rotate d) ] 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 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 (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 [subnet] (pins ...))`` entry.""" name: str subnet: int = 1 pins: list[NetPin] = field(default_factory=list) @dataclass class NetClass: """A ``(class 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 )``: 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