Source code for jitx.landpattern

"""
Landpattern and Pad definitions
===============================

This module provides classes for defining component landpatterns,
pads, and mappings between ports and pads.
"""

from __future__ import annotations
from collections.abc import Iterable, Sequence, Mapping
from typing import overload

from .net import Port
from .shapes import Shape
from .placement import Positionable
from .memo import memoize
from ._structural import Ref, Structural


[docs] @memoize class Landpattern(Positionable): """Component landpattern definition, also known as a footprint, package, or land-pattern. Defines the pads and associated geometry for the interface between an electrical component and the circuit board. >>> class ZigZagLandpattern(Landpattern): ... pad1 = MyPad().at(-0.5, 0) ... pad2 = MyPad().at(0.5, 0) ... ... silkscreen = Silkscreen( ... Polyline( ... 0.1, ... [ ... (-0.4, 1), (-0.3, 1.2), (-0.2, 0.8), ... (-0.1, 1.2), (0, 0.8), (0.1, 1.2), ... (0.2, 0.8), (0.3, 1.2), (0.4, 1), ... ] ... ) ... ) ... ... courtyard = Courtyard(rectangle(2, 1)) """ pass
[docs] class PadShape: """Shapes of various features of a pad.""" shape: Shape """The geometric shape of the pad.""" nfp: Shape | None = None """The geometric shape of the pad when non-functional pads are removed. When provided, it overrides the pad shape except on the top layer, bottom layer and intermediate copper layers that have traces or pours connected to the pad.""" def __init__( self, shape: Shape, *, nfp: Shape | None = None, ): self.shape = shape self.nfp = nfp
[docs] @memoize class Pad(Positionable): """Class representing a pad, user code should overload this class in order to create new pad definitions. If the pad contains a :py:class:`~jitx.feature.Cutout` it will be interpreted as a through-hole pad, otherwise it will be interpreted as a surface mount pad. >>> class MyPad(Pad): ... shape = Circle(diameter=0.8) ... shapes = { ... (0, 2): Circle(diameter=0.7), ... 1: PadShape(Circle(diameter=0.8), nfp=Circle(diameter=0.4)), ... -1: rectangle(2., 2.), ... } ... ... def __init__(self): ... self.cutout = Cutout(Circle(diameter=0.4) ... self.soldermask = Soldermask(self.shape) """ shape: Shape | PadShape """The geometric shape of the pad or a PadShape to specify the regular shape and the shape when non-functional pads are removed. Can be overridden on a per-layer basis by :py:attr:`~jitx.landpattern.Pad.shapes`.""" shapes: dict[int | tuple[int, ...], Shape | PadShape] = {} """The geometric shapes of the pad for specific layers. Overrides :py:attr:`~jitx.landpattern.Pad.shape` for the given layers."""
[docs] class PadMapping(Structural, Ref): """Mapping between component ports and landpattern pads. If no pad mapping is provided, a default mapping will be created that maps ports to pads in declaration order. If a port needs to be mapped to multiple pads, a PadMapping is required. >>> class MyComponent(Component): ... GND = Power() ... VIN = Power() ... VOUT = Power() ... ... lp = MyLandpattern() ... mappings = PadMapping({ ... GND: [lp.p[1], lp.p[4]], ... VIN: lp.p[3], ... VOUT: lp.p[3], ... }) >>> class MyComponent(Component): ... GND = Power() ... VIN = Power() ... VOUT = Power() ... ... lp = MyLandpattern() ... mappings = PadMapping({ ... GND: lp.p[1], ... VIN: lp.p[2], ... VOUT: lp.p[3], ... }) """ __entries: dict[Port, Pad | Sequence[Pad]] __inverse: dict[Pad, Port] def __init__( self, entries: Mapping[Port, Pad | Sequence[Pad]] | Iterable[tuple[Port, Pad | Sequence[Pad]]], ): """Initialize a pad mapping. Args: entries: Mapping or iterable of (port, pad) or (port, pad sequence) pairs. """ self.__entries = dict(entries) self.__inverse = {} for port, pad in self.__entries.items(): if isinstance(pad, Sequence): for p in pad: self.__inverse[p] = port else: self.__inverse[pad] = port @overload def __setitem__(self, port: Port, pad: Pad | Sequence[Pad], /): ... @overload def __setitem__(self, pad: Pad, port: Port, /): ... def __setitem__(self, port: Port | Pad, pad: Port | Pad | Sequence[Pad]): if isinstance(port, Pad): assert isinstance(pad, Port) other = self.__entries.get(pad) if isinstance(other, Sequence): self.__entries[pad] = tuple(other) + (port,) elif isinstance(other, Pad): self.__entries[pad] = (port, other) else: self.__entries[pad] = port self.__inverse[port] = pad elif isinstance(pad, Sequence): self.__entries[port] = pad for p in pad: self.__inverse[p] = port else: assert isinstance(pad, Pad) self.__entries[port] = pad self.__inverse[pad] = port @overload def __getitem__(self, port: Port) -> Pad | Sequence[Pad]: ... @overload def __getitem__(self, port: Pad) -> Port: ... def __getitem__(self, port: Port | Pad): if isinstance(port, Pad): return self.__inverse[port] return self.__entries[port] @overload def get[T]( self, port: Port, default: T = None, / ) -> Pad | Sequence[Pad] | Port | T: ... @overload def get[T](self, port: Pad, default: T = None, /) -> Port | T: ...
[docs] def get[T]( self, port: Port | Pad, default: T = None, / ) -> Pad | Sequence[Pad] | Port | T: """Return the pad or port associated with the given port or pad.""" if isinstance(port, Pad): return self.__inverse.get(port, default) return self.__entries.get(port, default)
def __contains__(self, port: Port | Pad): if isinstance(port, Pad): return port in self.__inverse return port in self.__entries def __len__(self): return len(self.__entries) def __iter__(self): return iter(self.__entries)
[docs] def items(self): return self.__entries.items()
[docs] def values(self): return self.__entries.values()
[docs] def inverse(self) -> Mapping[Pad, Port]: """Return the inverse mapping from pads to ports.""" return self.__inverse