Source code for jitxlib.landpatterns.leads

from dataclasses import dataclass
from collections.abc import Sequence
from typing import Self, overload

from jitx.toleranced import Toleranced

from ..ipc import DensityLevelMixin, DensityLevel, IPCRequirements, compute_ipc
from .protrusion import LeadProtrusion


[docs] @dataclass(frozen=True) class SMDLead: """Surface Mount Lead Descriptor""" length: Toleranced """Length of the SMT lead in mm This is typically the "foot length" of a lead. In JEDEC drawings this dimension is typically identified as 'L'. """ width: Toleranced """Width of the SMT lead in mm In JEDEC drawings this dimension is typically identified as 'b'. """ lead_type: LeadProtrusion """ Lead Protrusion Type This type captures typical features for this kind of lead. """ # TODO # override: Pad | None = None # """ Optional Override Pad for this Lead Type # """ def __post_init__(self): # TODO - check toleranced > 0 pass
[docs] def compute_constraints( self, lead_span: Toleranced, density_level: DensityLevel ) -> IPCRequirements: """Compute the IPC-7351B-based pad constraints for this lead type Args: lead_span: the lead span for the package density_level: the density level for the package Returns: the IPC computation results for this lead type and density level """ # TODO handle `override` here return compute_ipc( lead_span, self.length, self.width, self.lead_type.fillets[density_level] )
[docs] def pad_size(self, density_level: DensityLevel) -> tuple[float, float]: """Compute the pad size for this lead type Args: density_level: the density level for the package Returns: the pad size (width, height) for this lead type and density level """ fillet = self.lead_type.fillets[density_level] length = self.length.typ + fillet.toe + fillet.heel width = self.width.max_value + 2.0 * fillet.side return (length, width)
[docs] @dataclass(frozen=True) class THLead: """Through Hole Lead Descriptor This type of lead is typically used for axial through-hole resistors, radial through-hole capacitors, or DIP style ICs. """ length: Toleranced """Length of the through-hole lead in mm The length of the lead is measured from the body of the component to the end of the lead. For components where the lead lengths are different (for example, with electrolytic capacitors), this length describes the minimum common length. """ width: Toleranced """Width of the through-hole lead Usually the diameter of the lead, for rectangular leads this is the diagonal measurement of the lead. """ # TODO # override: Pad | None = None # """Override the generated through-hole pad with this explicit pad. # """ def __post_init__(self): # TODO - check toleranced > 0 pass
[docs] @dataclass class LeadPlacement: """This class encapsultes the data necessary to place the pads associated with a `LeadProfile` """ pad_size: tuple[float, float] """Pad Size as a (width, height) tuple where - `width` is aligned to the X-axis in mm - `height` is aligned to the Y-axis in mm """ center: float """Center to center distance in the lead-span direction. Value in mm. """ pitch: float """Center to Center distance between adjacent pads on one edge side. Value in mm. """
[docs] @dataclass(frozen=True) class LeadProfile: """Lead Profile Package Pad Descriptor This class describes the dimensions for a set of opposing pads on a package. For many IC packages, like dual packages (SOICs, SSOPs, etc) or quad packages (QFNs, QFPs, etc), there are one or more "lead profiles". In a dual package, there is typically one lead profile across the width of the dual row package. The lead profile describes: 1. Lead Span - this distance from the edge of the leads on one side to the edge of the leads on the other side of the component 2. Pitch - Distance between adjacent leads on the same side of the package. 3. Lead Type - the type of lead protrusions found on either side of the component. TODO - Diagram Here In a quad package, there are potentially two different lead profiles, one for left/right edges of the package and one for the top/bottom edges of the package. .. seealso:: IPC-7351B Figure 3-3 """ span: Toleranced """Lead Span - the distance from the edge of the leads on one side to the edge of the leads on the other side of the component, from outside edge of the lead to outside edge of the opposite lead. """ pitch: float """Pitch - the distance between adjacent leads on the same side of the package """ type: SMDLead """The lead type for this profile"""
[docs] def compute_placements(self, density_level: DensityLevel) -> LeadPlacement: """Compute the lead placements for this lead profile Args: density_level: the density level for the package Returns: the lead placements for this lead profile """ ipcData = self.type.compute_constraints(self.span, density_level) padSize = ipcData.pad_size() delta = ipcData.Gmin + padSize[0] # X return LeadPlacement(padSize, delta, self.pitch)
[docs] class LeadProfileMixin(DensityLevelMixin): __lead_profiles: Sequence[LeadProfile] | None = None @overload def lead_profile( self, lead_profile: LeadProfile, /, *additional_profiles: LeadProfile ) -> Self: ... @overload def lead_profile( self, pitch: float, span: Toleranced | float, length: Toleranced | float, width: float | Toleranced, protrusion: LeadProtrusion, ) -> Self: ...
[docs] def lead_profile( self, pitch: LeadProfile | float, span: float | Toleranced | LeadProfile = 0, length: float | Toleranced | LeadProfile = 0, width: float | Toleranced | LeadProfile = 0, protrusion: LeadProtrusion | None | LeadProfile = None, *additional_profiles: LeadProfile, ) -> Self: if isinstance(pitch, LeadProfile): lead_profiles = [pitch] if isinstance(span, LeadProfile): lead_profiles.append(span) if isinstance(length, LeadProfile): lead_profiles.append(length) if isinstance(width, LeadProfile): lead_profiles.append(width) if isinstance(protrusion, LeadProfile): lead_profiles.append(protrusion) if additional_profiles: lead_profiles.extend(additional_profiles) else: if not isinstance(span, Toleranced | float): raise TypeError("Span must be a float or Toleranced") if not isinstance(length, Toleranced | float): raise TypeError("Length must be a float or Toleranced") if not isinstance(width, Toleranced | float): raise TypeError("Width must be a float or Toleranced") if not isinstance(protrusion, LeadProtrusion | None): raise TypeError("Protrusion must be a LeadProtrusion") assert protrusion is not None lead_profile = LeadProfile( span=Toleranced.exact(span), pitch=pitch, type=SMDLead( length=Toleranced.exact(length), width=Toleranced.exact(width), lead_type=protrusion, ), ) lead_profiles = (lead_profile,) self.__lead_profiles = lead_profiles return self
def _lead_profiles(self) -> Sequence[LeadProfile]: if self.__lead_profiles is None: raise ValueError("No lead profile specified") return self.__lead_profiles @property def _lead_profiles_optional(self) -> Sequence[LeadProfile] | None: return self.__lead_profiles def _lead_placements(self) -> Sequence[LeadPlacement]: profiles = self._lead_profiles() if profiles is None: raise ValueError("No lead profile specified") return tuple(p.compute_placements(self._density_level) for p in profiles)