Source code for jitxlib.symbols.transistor.jfet

"""
JFET transistor symbols for JITX Standard Library

This module provides JFET (Junction Field Effect Transistor) symbol definitions
for N-Channel and P-Channel transistors (always depletion mode).
"""

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

from jitx.shapes import Shape
from jitx.shapes.primitive import Arc, ArcPolyline, Polyline
from jitx.symbol import Direction, Pin

from ..arrow import Arrow, ArrowConfig, ArrowConfigurable
from ..label import LabelConfigurable, LabelledSymbol
from .fet import FETJunction

if TYPE_CHECKING:
    from ..context import SymbolStyleContext


# JFET constants
DEF_JFET_PITCH = 3.0  # Drain to source distance
DEF_JFET_WIDTH = 2.0  # Distance from D/S line to gate pin
DEF_JFET_PORCH_WIDTH = 0.5
DEF_JFET_BASE_LINE = 2.4
DEF_JFET_SD_CHANNEL_WIDTH = 0.8  # Width of source-drain channel body
DEF_JFET_ARROW_OFFSET = 0.1  # 10% offset along gate line
DEF_JFET_OUTLINE = True
DEF_JFET_LINE_WIDTH = 0.1


[docs] @dataclass class JFETConfig(LabelConfigurable, ArrowConfigurable): """ Configuration for JFET symbols Defines the geometric and visual parameters for JFET symbols. JFETs are always depletion-mode devices (solid channel line). """ pitch: float = DEF_JFET_PITCH """Distance between drain and source pins""" width: float = DEF_JFET_WIDTH """Distance from drain/source line to gate pin""" porch_width: float = DEF_JFET_PORCH_WIDTH """Length of porch lines from pins to channel""" base_line_length: float = DEF_JFET_BASE_LINE """Length of the channel structure""" sd_channel_width: float = DEF_JFET_SD_CHANNEL_WIDTH """Width of the source-drain channel body""" arrow_offset: float = DEF_JFET_ARROW_OFFSET """Offset of arrow along gate-to-channel line (as fraction)""" outline: bool = DEF_JFET_OUTLINE """Whether to draw circle outline around transistor""" line_width: float = DEF_JFET_LINE_WIDTH """Width of symbol lines"""
[docs] class JFETSymbol[T: JFETConfig](LabelledSymbol): """ JFET symbol with graphics and pins. The JFET symbol consists of: - Drain and source channel lines - Solid channel (JFETs are always depletion mode) - Gate arrow pointing into or out of channel - Optional circle outline Pins: 'G' (gate), 'D' (drain), 'S' (source) """ config: T junction_type: FETJunction drain_line: Shape[Polyline] source_line: Shape[Polyline] drain_porch: Shape[Polyline] source_porch: Shape[Polyline] channel: Shape[Polyline] gate_arrow: Arrow outline_circle: Shape[ArcPolyline] | None G: Pin # Gate D: Pin # Drain S: Pin # Source def _symbol_style_config(self, context: SymbolStyleContext | None = None) -> T: """Symbol style config for this JFET symbol.""" if context is None: config = JFETConfig() else: config = context.jfet_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, junction_type: FETJunction = FETJunction.N_CHANNEL, config: T | None = None, **kwargs, ): """ Initialize JFET symbol. Args: junction_type: N_CHANNEL or P_CHANNEL 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.junction_type = junction_type self._build_jfet_glyphs() self._build_pins() self._build_labels(ref=Direction.Right, value=Direction.Right) def _build_jfet_glyphs(self) -> None: """Build the JFET transistor glyphs following Stanza implementation.""" p2 = self.pitch / 2.0 porch = self.porch_width base_len = self.base_line_length lw = self.line_width sd_len = self.sd_channel_width # Calculate porch endpoints (same as FET) drain_porch_y = p2 - porch source_porch_y = -(p2 - porch) # Drain and source horizontal lines (Stanza lines 137-144) # line-ch from (0,0) to (-sd_len, 0), positioned at porch heights self.drain_line = Polyline(lw, [(0.0, drain_porch_y), (-sd_len, drain_porch_y)]) self.source_line = Polyline( lw, [(0.0, source_porch_y), (-sd_len, source_porch_y)] ) # Porches (Stanza lines 146-152) # Drain porch: from drain_porch_y up to drain pin (and slightly beyond) porch_extension = 0.4 self.drain_porch = Polyline( lw, [(0.0, drain_porch_y), (0.0, p2 + porch_extension)] ) # Source porch: from source_porch_y down to source pin (and slightly beyond) self.source_porch = Polyline( lw, [(0.0, source_porch_y), (0.0, -p2 - porch_extension)] ) # Channel (Stanza lines 154-162) - always solid for JFET base_x = -sd_len self.channel = Polyline( lw, [(base_x, base_len / 2.0), (base_x, -base_len / 2.0)] ) # Gate arrow (Stanza lines 164-184) # This is complex: shaft + arrow with offset arrow_config = self.config.get_arrow_config() if arrow_config is None: from ..arrow import ArrowStyle arrow_config = ArrowConfig( style=ArrowStyle.OPEN_ARROW, shaft_length=0.8, head_dims=(0.5, 0.5), line_width=lw, ) # Calculate gate pin Y position gate_y_raw = p2 - porch if self.junction_type == FETJunction.N_CHANNEL: gate_y = -gate_y_raw else: gate_y = gate_y_raw # Total length from channel to gate pin total_len = abs(-self.width) - sd_len offset = total_len * self.arrow_offset shaft_len = total_len - offset # Update arrow config with computed shaft length arrow_config = replace(arrow_config, shaft_length=shaft_len) if self.junction_type == FETJunction.N_CHANNEL: # N-channel: at (-sd_len, gate_y), rotated 180° (pointing left toward gate) self.gate_arrow = Arrow((-sd_len, gate_y), 180.0, arrow_config) else: # P-channel: at (-(sd_len + total_len), gate_y), pointing right (toward channel) self.gate_arrow = Arrow((-(sd_len + total_len), gate_y), 0.0, arrow_config) # Optional outline circle if self.outline: self._build_outline_circle() else: self.outline_circle = None def _build_outline_circle(self) -> None: """Build the optional circle outline around the transistor.""" p2 = self.pitch / 2.0 # Center between gate pin and drain/source pins center_x = -self.width / 2.0 center_y = 0.0 # Radius to farthest pin porch = self.porch_width gate_y_raw = p2 - porch if self.junction_type == FETJunction.N_CHANNEL: gate_y = -gate_y_raw else: gate_y = gate_y_raw radius_to_gate = math.sqrt( (center_x - (-self.width)) ** 2 + (center_y - gate_y) ** 2 ) radius_to_ds = math.sqrt(center_x**2 + p2**2) radius = max(radius_to_gate, radius_to_ds) # Add margin for visual clearance radius *= 1.2 # Create circle with Arc at the calculated center position self.outline_circle = ArcPolyline( self.line_width, [Arc((center_x, center_y), radius, 0.0, 360.0)] ) def _build_pins(self) -> None: """Build 'G', 'D', 'S' symbol pins.""" p2 = self.pitch / 2.0 porch = self.porch_width # Gate pin position depends on channel type (matches FET-pin-positions) gate_y_raw = p2 - porch if self.junction_type == FETJunction.N_CHANNEL: gate_y = -gate_y_raw else: gate_y = gate_y_raw # Pin positions using exact float values to match porch geometry # GridPoint type hint expects int, but JITX accepts float (like Stanza) self.G = Pin(at=(-self.width, gate_y), direction=Direction.Left) # type: ignore if self.junction_type == FETJunction.N_CHANNEL: # N-channel: Drain at top, Source at bottom self.D = Pin(at=(0, p2), direction=Direction.Up) # type: ignore self.S = Pin(at=(0, -p2), direction=Direction.Down) # type: ignore else: # P-channel: Source at top, Drain at bottom self.S = Pin(at=(0, p2), direction=Direction.Up) # type: ignore self.D = Pin(at=(0, -p2), direction=Direction.Down) # type: ignore @property def pitch(self) -> float: """See :attr:`~.JFETConfig.pitch`.""" return self.config.pitch @property def width(self) -> float: """See :attr:`~.JFETConfig.width`.""" return self.config.width @property def porch_width(self) -> float: """See :attr:`~.JFETConfig.porch_width`.""" return self.config.porch_width @property def base_line_length(self) -> float: """See :attr:`~.JFETConfig.base_line_length`.""" return self.config.base_line_length @property def sd_channel_width(self) -> float: """See :attr:`~.JFETConfig.sd_channel_width`.""" return self.config.sd_channel_width @property def arrow_offset(self) -> float: """See :attr:`~.JFETConfig.arrow_offset`.""" return self.config.arrow_offset @property def outline(self) -> bool: """See :attr:`~.JFETConfig.outline`.""" return self.config.outline @property def line_width(self) -> float: """See :attr:`~.JFETConfig.line_width`.""" return self.config.line_width @property def label_config(self) -> LabelConfigurable: """Configuration object that provides label configuration""" return self.config