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