"""
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