Source code for jitxlib.landpatterns.ipc

from __future__ import annotations
from dataclasses import dataclass
from enum import Enum
from typing import TYPE_CHECKING, ClassVar, Literal, Self
import math

from jitx.context import Context
from jitx.toleranced import Toleranced
from jitx._structural import Structurable

if TYPE_CHECKING:
    from .leads.fillets import LeadFillets


[docs] class DensityLevel(Enum): """Density Level This enum specifies the density level for footprints on a PCB. Higher density levels indicate less space between landpatterns. These are defined in IPC-7351. """ A = "A" """Density Level A Maximum land protrusion """ B = "B" """Density Level B Median (nominal) land protrusion """ C = "C" """Density Level C Minimum land protrusion """
[docs] @dataclass class IPCRequirements: """IPC Formula Results from Section 3 of IPC 7351B This class contains the results of the IPC formula for computing the pad size. TODO: Add Diagram here """ Zmax: float """Maximum overall length""" Gmin: float """Minimum distance between pads""" Xmin: float """Minimum pad width"""
[docs] def pad_size(self) -> tuple[float, float]: """Compute the pad dimensions from this IPC result Returns: The pad dimensions as (y, x)""" return (0.5 * (self.Zmax - self.Gmin), self.Xmin)
[docs] def compute_ipc( leadSpan: Toleranced, leadLength: Toleranced, leadWidth: Toleranced, fillets: LeadFillets, ) -> IPCRequirements: """Compute Pad Geometry According to IPC Rules Args: leadSpan: edge-of-lead to edge-of-lead distance for an IC package. leadLength: length of the exposed contact in the same dimension as `leadSpan` leadWidth: width of the exposed contact in the dimension orthogonal to `leadSpan` fillets: Specifications for the solder fillets created when soldering the lead to the land-pattern pad. These parameters define extra spacing around the lead dimension to form these fillets. Returns: IPCResult containing the results of the IPC formula Note: IPC-7351B section 3.1.5 states the formulas with two extra terms inside the root-sum-square -- ``F`` (printed-board fabrication tolerance) and ``P`` (part placement accuracy):: Zmax = Lmin + 2*JT + sqrt(CL**2 + F**2 + P**2) Gmin = Smax - 2*JH - sqrt(CS**2 + F**2 + P**2) Xmax = Wmin + 2*JS + sqrt(CW**2 + F**2 + P**2) This implementation is those formulas with ``F = P = 0``. Rev B specifies no normative values for either -- it defers them to the IPC-7351 land pattern libraries and tells users to set them for their own fabricator and placement equipment. It also explicitly recommends ``F = 0`` when the fabricator already applies etch-factor compensation, so that term is well founded; ``P = 0`` is a simplification. Rev B tables 3-25 (finished conductor width tolerances) and 3-26 (feature location accuracy) are the starting points if these are ever made configurable. Note that the A/B/C levels in those two tables are *producibility* levels, not the density levels used here. """ Jt = fillets.toe Jh = fillets.heel Js = fillets.side Lmax = leadSpan.max_value Lmin = leadSpan.min_value Wmin = leadWidth.min_value Tmin = leadLength.min_value Smax = Lmax - 2.0 * Tmin C_L = leadSpan.range() C_W = leadWidth.range() C_T = leadLength.range() C_S = math.sqrt(C_L * C_L + C_T * C_T) # the distance from edge of land to edge of land on the exterior of the land pattern Zmax = Lmin + 2.0 * Jt + C_L # the distance from edge of land to edge of land on the interior of the land pattern Gmin = Smax - 2.0 * Jh - C_S # the size of the land in the dimension orthogonal to Z and G. Xmin = Wmin + 2.0 * Js + C_W return IPCRequirements(Zmax, Gmin, Xmin)
[docs] @dataclass(frozen=True) class DensityLevelContext(Context): _global_default: ClassVar[DensityLevelContext] density_level: DensityLevel
[docs] @classmethod def get(cls) -> DensityLevelContext: # do not construct a new object here, it'll taint caching mechanisms. return super().get() or DensityLevelContext._global_default
# Level B is IPC-7351B's "Median (Nominal)" land protrusion and the general-purpose # choice for most assemblies. Level C is the minimum-protrusion goal, intended for # high-density designs that have accepted the reduced solder-joint margin, so it is a # poor thing to get by default. Override with a DensityLevelContext for a design, or # with DensityLevelMixin.density_level() for a single landpattern. DensityLevelContext._global_default = DensityLevelContext(DensityLevel.B) # make use of contexts, so much be instantiated in context.
[docs] class DensityLevelMixin(Structurable): __density_level: DensityLevel | None = None __density_level_ctx: DensityLevel if not TYPE_CHECKING: def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.__density_level_ctx = DensityLevelContext.get().density_level
[docs] def density_level( self, density_level: DensityLevel | Literal["A"] | Literal["B"] | Literal["C"] ) -> Self: if isinstance(density_level, str): density_level = DensityLevel[density_level] self.__density_level = density_level return self
@property def _density_level(self) -> DensityLevel: return self.__density_level or self.__density_level_ctx