Source code for jitxlib.symbols.diode.diode

"""
Diode symbols for JITX Standard Library

This module provides diode symbol definitions including standard diodes
and variants like Schottky, Zener, Tunnel, and TVS diodes.
"""

from __future__ import annotations
from dataclasses import dataclass, replace
from typing import TYPE_CHECKING, cast, override

from jitx.shapes import Shape
from jitx.shapes.primitive import Polygon, Polyline
from jitx.symbol import Direction, Pin
from jitx.transform import Transform

from ..label import LabelConfigurable, LabelledSymbol

if TYPE_CHECKING:
    from ..context import SymbolStyleContext


# Diode constants
DEF_DIODE_PITCH = 2.0
DEF_DIODE_BODY_WIDTH = 0.6
DEF_DIODE_LINE_WIDTH = 0.05
DEF_DIODE_FILLED = False
DEF_WING_SIZE = 0.1


[docs] @dataclass class DiodeConfig(LabelConfigurable): """ Configuration for diode symbols Defines the geometric and visual parameters for diode symbols. """ pitch: float = DEF_DIODE_PITCH """Distance between pin points (anode to cathode)""" body_width: float = DEF_DIODE_BODY_WIDTH """Width of the diode triangle body""" body_height: float | None = None """Height of the diode triangle body (if None, uses body_width for square aspect)""" line_width: float = DEF_DIODE_LINE_WIDTH """Width of the diode lines""" filled: bool = DEF_DIODE_FILLED """Whether the triangle body is filled"""
[docs] class DiodeSymbol[T: DiodeConfig](LabelledSymbol): """ Diode symbol with graphics and pins. The standard diode symbol consists of a triangle body pointing toward the cathode, with a T-bar at the cathode side. Pins: 'a' (anode, top), 'c' (cathode, bottom) """ config: T triangle_body: Shape[Polyline] | Shape[Polygon] t_bar: Shape[Polyline] front_porch: Shape[Polyline] back_porch: Shape[Polyline] a: Pin # Anode (top) c: Pin # Cathode (bottom) def _symbol_style_config(self, context: SymbolStyleContext | None = None) -> T: """Symbol style config for this diode symbol.""" if context is None: config = DiodeConfig() else: config = context.diode_config return cast(T, config) def _lookup_config( self, config: T | None = None, context: SymbolStyleContext | None = None ) -> T: """Lookup the config for this symbol.""" if config is None: return self._symbol_style_config(context) return config def __init__(self, config: T | None = None, **kwargs): """ Initialize diode symbol. Args: config: Config object, or None to use defaults **kwargs: Individual parameters to override defaults """ from ..context import SymbolStyleContext context = SymbolStyleContext.get() config = self._lookup_config(config, context) self.config = replace(config, **kwargs) self._build_diode_glyphs() self._build_pins() self._build_labels(ref=Direction.Right, value=Direction.Right) def _get_body_height(self) -> float: """Get the body height, defaulting to body_width if not specified.""" if self.config.body_height is None: return self.config.body_width return self.config.body_height def _build_diode_glyphs(self) -> None: """Build the diode triangle body, T-bar, and porch lines.""" bw2 = self.body_width / 2.0 h = self._get_body_height() / 2.0 p2 = self.pitch / 2.0 # Validate that body isn't too large for the pitch if h > p2: raise ValueError( f"Invalid Diode Symbol Dimension: Body is too Large: " f"body_height={h * 2} pitch={self.pitch}" ) # Extend porches beyond pins to ensure visual connection with wires # This accounts for any pin rendering offset or connection point positioning porch_extension = 0.2 # Front porch (cathode side) - from beyond cathode pin to T-bar self.front_porch = Polyline( self.line_width, [(0.0, -p2 - porch_extension), (0.0, -h)] ) # T-bar at cathode (y = -h) self.t_bar = Polyline(self.line_width, [(-bw2, -h), (bw2, -h)]) # Triangle body - tip at cathode side (bottom), base at anode side (top) # Points: tip at (0, -h), base corners at (bw2, h) and (-bw2, h) tri_pts = [(0.0, -h), (bw2, h), (-bw2, h), (0.0, -h)] if self.filled: self.triangle_body = Polygon(tri_pts) else: self.triangle_body = Polyline(self.line_width, tri_pts) # Back porch (anode side) - from triangle base to beyond anode pin self.back_porch = Polyline( self.line_width, [(0.0, h), (0.0, p2 + porch_extension)] ) def _build_pins(self) -> None: """Build 'a' (anode) and 'c' (cathode) symbol pins.""" p2 = self.pitch / 2.0 self.a = Pin(at=(0.0, p2), direction=Direction.Up) # type: ignore self.c = Pin(at=(0.0, -p2), direction=Direction.Down) # type: ignore @property def pitch(self) -> float: """See :attr:`~.DiodeConfig.pitch`.""" return self.config.pitch @property def body_width(self) -> float: """See :attr:`~.DiodeConfig.body_width`.""" return self.config.body_width @property def body_height(self) -> float | None: """See :attr:`~.DiodeConfig.body_height`.""" return self.config.body_height @property def line_width(self) -> float: """See :attr:`~.DiodeConfig.line_width`.""" return self.config.line_width @property def filled(self) -> bool: """See :attr:`~.DiodeConfig.filled`.""" return self.config.filled @property def label_config(self) -> LabelConfigurable: """Configuration object that provides label configuration""" return self.config
[docs] @dataclass class SchottkyDiodeConfig(DiodeConfig): """ Configuration for Schottky diode symbols Extends DiodeConfig with L-shaped wing decorations at the cathode bar. """ wing_size: float = DEF_WING_SIZE """Size of the L-shaped wing decorations"""
[docs] class SchottkyDiodeSymbol[T: SchottkyDiodeConfig](DiodeSymbol): """ Schottky diode symbol with L-shaped wings at cathode bar. Pins: 'a' (anode), 'c' (cathode) """ l_wing: Shape[Polyline] r_wing: Shape[Polyline] @override def _symbol_style_config( self, context: SymbolStyleContext | None = None ) -> SchottkyDiodeConfig: """Symbol style config for Schottky diode.""" if context is None: return SchottkyDiodeConfig() return context.schottky_diode_config @override def _build_diode_glyphs(self) -> None: """Build Schottky diode glyphs with L-shaped wings.""" super()._build_diode_glyphs() bw2 = self.body_width / 2.0 h = self._get_body_height() / 2.0 wing = self.wing_size # L-shaped wing on left side: vertical down then horizontal right l_wing_pts = [(0.0, 0.0), (0.0, -wing), (wing, -wing)] self.l_wing = Transform((-bw2, -h)) * Polyline(self.line_width, l_wing_pts) # L-shaped wing on right side: rotated 180 degrees r_wing_pts = [(0.0, 0.0), (0.0, -wing), (wing, -wing)] self.r_wing = Transform((bw2, -h), 180.0) * Polyline( self.line_width, r_wing_pts ) @property def wing_size(self) -> float: """See :attr:`~.SchottkyDiodeConfig.wing_size`.""" return self.config.wing_size
[docs] @dataclass class ZenerDiodeConfig(DiodeConfig): """ Configuration for Zener diode symbols Extends DiodeConfig with diagonal wing decorations at the cathode bar. """ wing_size: float = DEF_WING_SIZE """Size of the diagonal wing decorations"""
[docs] class ZenerDiodeSymbol[T: ZenerDiodeConfig](DiodeSymbol): """ Zener diode symbol with diagonal wings at cathode bar. Pins: 'a' (anode), 'c' (cathode) """ l_wing: Shape[Polyline] r_wing: Shape[Polyline] @override def _symbol_style_config( self, context: SymbolStyleContext | None = None ) -> ZenerDiodeConfig: """Symbol style config for Zener diode.""" if context is None: return ZenerDiodeConfig() return context.zener_diode_config @override def _build_diode_glyphs(self) -> None: """Build Zener diode glyphs with diagonal wings.""" super()._build_diode_glyphs() bw2 = self.body_width / 2.0 h = self._get_body_height() / 2.0 wing = self.wing_size # Diagonal wings at the ends of the T-bar # Both use the same shape: from (0,0) to (-wing, -wing) - down and left at 45° wing_pts = [(0.0, 0.0), (-wing, -wing)] # Left wing: positioned at left end of T-bar self.l_wing = Transform((-bw2, -h)) * Polyline(self.line_width, wing_pts) # Right wing: same shape rotated 180° and positioned at right end of T-bar self.r_wing = Transform((bw2, -h), 180.0) * Polyline(self.line_width, wing_pts) @property def wing_size(self) -> float: """See :attr:`~.ZenerDiodeConfig.wing_size`.""" return self.config.wing_size
[docs] @dataclass class TunnelDiodeConfig(DiodeConfig): """ Configuration for Tunnel diode symbols Extends DiodeConfig with vertical wing decorations at the cathode bar. """ wing_size: float = DEF_WING_SIZE """Size of the vertical wing decorations"""
[docs] class TunnelDiodeSymbol[T: TunnelDiodeConfig](DiodeSymbol): """ Tunnel diode symbol with vertical wings at cathode bar. Pins: 'a' (anode), 'c' (cathode) """ l_wing: Shape[Polyline] r_wing: Shape[Polyline] @override def _symbol_style_config( self, context: SymbolStyleContext | None = None ) -> TunnelDiodeConfig: """Symbol style config for Tunnel diode.""" if context is None: return TunnelDiodeConfig() return context.tunnel_diode_config @override def _build_diode_glyphs(self) -> None: """Build Tunnel diode glyphs with vertical wings.""" super()._build_diode_glyphs() bw2 = self.body_width / 2.0 h = self._get_body_height() / 2.0 wing = self.wing_size # Vertical wing on left side: goes straight up l_wing_pts = [(0.0, 0.0), (0.0, wing)] self.l_wing = Transform((-bw2, -h)) * Polyline(self.line_width, l_wing_pts) # Vertical wing on right side: goes straight up r_wing_pts = [(0.0, 0.0), (0.0, wing)] self.r_wing = Transform((bw2, -h)) * Polyline(self.line_width, r_wing_pts) @property def wing_size(self) -> float: """See :attr:`~.TunnelDiodeConfig.wing_size`.""" return self.config.wing_size
[docs] @dataclass class TVSDiodeConfig(DiodeConfig): """ Configuration for TVS diode symbols Extends DiodeConfig with dual opposing triangles and wing decorations. """ wing_size: float = DEF_WING_SIZE """Size of the wing decorations at the junction"""
[docs] class TVSDiodeSymbol[T: TVSDiodeConfig](LabelledSymbol): """ TVS (Transient Voltage Suppressor) diode symbol with dual opposing triangles. The TVS symbol consists of two triangles pointing at each other (back-to-back diodes) with wing decorations at the junction. Pins: 'a' (anode), 'c' (cathode) """ config: T top_triangle: Shape[Polyline] | Shape[Polygon] bottom_triangle: Shape[Polyline] | Shape[Polygon] l_wing: Shape[Polyline] r_wing: Shape[Polyline] front_porch: Shape[Polyline] back_porch: Shape[Polyline] a: Pin c: Pin def _symbol_style_config(self, context: SymbolStyleContext | None = None) -> T: """Symbol style config for TVS diode.""" if context is None: config = TVSDiodeConfig() else: config = context.tvs_diode_config return cast(T, config) def _lookup_config( self, config: T | None = None, context: SymbolStyleContext | None = None ) -> T: """Lookup the config for this symbol.""" if config is None: return self._symbol_style_config(context) return config def __init__(self, config: T | None = None, **kwargs): """Initialize TVS diode symbol.""" from ..context import SymbolStyleContext context = SymbolStyleContext.get() config = self._lookup_config(config, context) self.config = replace(config, **kwargs) self._build_tvs_glyphs() self._build_pins() self._build_labels(ref=Direction.Right, value=Direction.Right) def _get_body_height(self) -> float: """Get the body height, defaulting to body_width if not specified.""" if self.config.body_height is None: return self.config.body_width return self.config.body_height def _build_tvs_glyphs(self) -> None: """Build TVS diode glyphs with dual opposing triangles and wings.""" bw2 = self.body_width / 2.0 h = self._get_body_height() / 2.0 h2 = h / 2.0 p2 = self.pitch / 2.0 wing = self.wing_size # Extend porches beyond pins to ensure visual connection with wires # This accounts for any pin rendering offset or connection point positioning porch_extension = 0.2 # Front porch (cathode side) - from beyond cathode pin to bottom triangle self.front_porch = Polyline( self.line_width, [(0.0, -p2 - porch_extension), (0.0, -h)] ) # Create one triangle template: tip at (0, -h2), base at (±bw2, h2) # This triangle spans height h, with tip at bottom tri_pts = [(0.0, -h2), (bw2, h2), (-bw2, h2), (0.0, -h2)] if self.filled: half_tri = Polygon(tri_pts) else: half_tri = Polyline(self.line_width, tri_pts) # Top triangle: translate up by h2 so tip is at 0, base at h self.top_triangle = Transform((0.0, h2)) * half_tri # Bottom triangle: translate down by h2 and rotate 180° so tip is at 0, base at -h self.bottom_triangle = Transform((0.0, -h2), 180.0) * half_tri # Wings at the junction (y = 0) - create "Z" shape at center # Wing shape: horizontal from bw2 to 0, then diagonal to (-wing, -wing) wing_pts = [(bw2, 0.0), (0.0, 0.0), (-wing, -wing)] # Left wing at (-bw2, 0) self.l_wing = Transform((-bw2, 0.0)) * Polyline(self.line_width, wing_pts) # Right wing at (bw2, 0), rotated 180° self.r_wing = Transform((bw2, 0.0), 180.0) * Polyline(self.line_width, wing_pts) # Back porch (anode side) - from top triangle to beyond anode pin self.back_porch = Polyline( self.line_width, [(0.0, h), (0.0, p2 + porch_extension)] ) def _build_pins(self) -> None: """Build 'a' (anode) and 'c' (cathode) symbol pins.""" p2 = self.pitch / 2.0 self.a = Pin(at=(0.0, p2), direction=Direction.Up) # type: ignore self.c = Pin(at=(0.0, -p2), direction=Direction.Down) # type: ignore @property def pitch(self) -> float: """See :attr:`~.TVSDiodeConfig.pitch`.""" return self.config.pitch @property def body_width(self) -> float: """See :attr:`~.TVSDiodeConfig.body_width`.""" return self.config.body_width @property def body_height(self) -> float | None: """See :attr:`~.TVSDiodeConfig.body_height`.""" return self.config.body_height @property def line_width(self) -> float: """See :attr:`~.TVSDiodeConfig.line_width`.""" return self.config.line_width @property def filled(self) -> bool: """See :attr:`~.TVSDiodeConfig.filled`.""" return self.config.filled @property def wing_size(self) -> float: """See :attr:`~.TVSDiodeConfig.wing_size`.""" return self.config.wing_size @property def label_config(self) -> LabelConfigurable: """Configuration object that provides label configuration""" return self.config