Source code for jitxlib.landpatterns.courtyard

from __future__ import annotations
from typing import override

from jitx.shapes import Shape

from .package import PackageBodyMixin
from . import ApplyToMixin, LandpatternGenerator, LandpatternProvider, LineWidthMixin
from .leads import LeadProfileMixin
from .ipc import DensityLevel, DensityLevelMixin
from .leads import LeadProfile

from jitx.feature import Feature, Custom, Soldermask, Courtyard
from jitx.shapes.composites import (
    bounds_area,
    bounds_dimensions,
    buffer_bounds,
    plus_symbol,
    rectangle_from_bounds,
)


[docs] class CourtyardGenerator:
[docs] def make_courtyard(self, target: CourtyardGeneratorMixin) -> Courtyard: raise NotImplementedError(f"{self.__class__.__name__}.make_courtyard")
[docs] class CourtyardGeneratorMixin( ApplyToMixin, PackageBodyMixin, LeadProfileMixin, DensityLevelMixin, LandpatternProvider, ): __generator: CourtyardGenerator | None = None _courtyard: Courtyard | None = None
[docs] def courtyard(self, generator: CourtyardGenerator): """Add a courtyard generator to the landpattern""" self.__generator = generator return self
@override def _build(self): # clear out so it's not discovered by others mixins for the first time # on the second run self._courtyard = None super()._build() @override def _build_decorate(self): super()._build_decorate() if self.__generator is not None: self._courtyard = self.__generator.make_courtyard(self)
[docs] class OriginMarkerMixin(ApplyToMixin, LineWidthMixin, LandpatternProvider): __size: float = 1 __ratio: float = 0.5 _origin_marker: Feature | None = None
[docs] def origin_marker( self, max_size: float | None = None, max_ratio: float | None = None ): if max_size is not None: self.__size = max_size if max_ratio is not None: self.__ratio = max_ratio return self
@override def _build(self): self._origin_marker = None super()._build() @override def _build_decorate(self): super()._build_decorate() line_width = self._line_width size = self.__size ratio = self.__ratio bounds = self._applies_to_bounds(Soldermask) w, h = bounds_dimensions(bounds) lower_dim = min(w, h) size = min(size, lower_dim * ratio) plus_shape = plus_symbol(length=size, line_width=line_width) self._origin_marker = Custom(shape=plus_shape, name="Origin")
[docs] def courtyard_excess(density_level: DensityLevel) -> float: """Compute the courtyard excess for a density level Values are taken from IPC-7351B Table 3-17. Args: density_level: the density level to compute the courtyard excess for Return: the courtyard excess (buffer distance from pad features to courtyard shape) based on the density level. """ match density_level: case DensityLevel.A: return 2.0 case DensityLevel.B: return 1.0 case DensityLevel.C: return 0.5
[docs] def lead_profile_courtyard_excess( lead_profile: LeadProfile, density_level: DensityLevel, ) -> float: """Compute the courtyard excess for a lead profile Args: lead_profile: the lead profile to compute the courtyard excess for density_level: the density level to compute the courtyard excess for Returns: the courtyard excess based on the lead profile and density level. """ lead_type = lead_profile.type.lead_type return lead_type.fillets[density_level].courtyard_excess
[docs] class ExcessCourtyardGenerator(CourtyardGenerator): def __init__(self, excess: float | None = None): self.__excess = excess def __pick_excess(self, target: CourtyardGeneratorMixin): if self.__excess is not None: return self.__excess density_level = target._density_level lead_profiles = target._lead_profiles_optional if lead_profiles: return max( lead_profile_courtyard_excess(lp, density_level) for lp in lead_profiles ) return courtyard_excess(density_level)
[docs] def make_courtyard(self, target: CourtyardGeneratorMixin): extra: Shape | None = None pb = target._package_body_optional if pb: extra = pb.envelope(target._density_level) bounds = target._applies_to_bounds(Soldermask, additional=extra) excess = self.__pick_excess(target) if bounds_area(bounds) <= 0.0: raise ValueError("Feature bounds are empty") ctyd_shape = rectangle_from_bounds(buffer_bounds(bounds, excess)) return Courtyard(ctyd_shape)
[docs] class ExcessCourtyard(CourtyardGeneratorMixin, LandpatternGenerator): """A courtyard generator mixin with a default ExcessCourtyard generator.""" @override def __base_init__(self): super().__base_init__() self.courtyard(ExcessCourtyardGenerator())