Source code for jitxlib.symbols.capacitor.capacitor

"""
Capacitor symbol for JITX Standard Library
"""

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

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

from jitx.transform import Transform
from jitx.units import percent, Quantity
from ..common import DEF_LINE_WIDTH
from ..label import LabelConfigurable, LabelledSymbol

if TYPE_CHECKING:
    from ..context import SymbolStyleContext


def _resolve_value(value: float, reference: float) -> float:
    """Resolve a value that could be absolute or percentage"""
    if isinstance(value, Quantity):
        if str(value.units) == "percent":
            # Convert percentage to absolute
            return value.magnitude * reference / 100.0
        else:
            # Other units - just use magnitude
            return float(value.magnitude)
    else:
        # Plain float/int - use as absolute
        return float(value)


[docs] class PolarizedStyle(Enum): """ Polarized capacitor symbol styles Defines the visual representation style for polarized capacitor symbols. """ STRAIGHT = "straight" CURVED = "curved"
# CapacitorConfig constants DEF_CAP_PITCH = 4.0 DEF_CAP_PORCH_WIDTH = 80 * percent # 80% of pitch/2 DEF_CAP_WIDTH = 3.0 DEF_CAP_POL_STYLE = PolarizedStyle.STRAIGHT DEF_CAP_POL_RADIUS = 5.0 DEF_CAP_PLUS_SIZE = 20 * percent # 20% of width
[docs] @dataclass class CapacitorConfig(LabelConfigurable): """ Configuration for capacitor symbols Defines the geometric and visual parameters for capacitor symbols. """ pitch: float = DEF_CAP_PITCH """Distance between pin points""" width: float = DEF_CAP_WIDTH """Width of the capacitor plates""" porch_width: float = DEF_CAP_PORCH_WIDTH """Length of line from pin to capacitor plate (absolute or percentage of pitch/2)""" line_width: float = DEF_LINE_WIDTH """Width of the capacitor lines"""
[docs] class CapacitorSymbol[T: CapacitorConfig](LabelledSymbol): """ Capacitor symbol with graphics and pins. Also serves as the base class for all other capacitor symbol types. """ config: T capacitor_top_plate: tuple[Shape[Polyline], Shape[Polyline]] # (porch, plate) capacitor_bottom_plate: tuple[ Shape[Polyline], Shape[Polyline] | Shape[ArcPolyline] ] # (porch, plate) def _symbol_style_config(self, context: SymbolStyleContext | None = None) -> T: """Symbol style config for this capacitor symbol.""" if context is None: config = CapacitorConfig() else: config = context.capacitor_config # Casting necessary because CapacitorSymbol is both a generic and a concrete class. 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 # TODO: Look into making kwargs type-specific using a paramspec (same for resistor class). def __init__(self, config: T | None = None, **kwargs): """ Initialize capacitor symbol Args: config: Config object, or None to use defaults **kwargs: Individual parameters to override defaults """ # Apparently this needs to be imported here, even though SymbolStyleContext is imported in TYPE_CHECKING. from ..context import SymbolStyleContext context = SymbolStyleContext.get() config = self._lookup_config(config, context) self.config = replace(config, **kwargs) self._build_capacitor_plates() self._build_pins() self._build_labels(ref=Direction.Right, value=Direction.Right) def _build_pins(self) -> None: """Build 'p[1]' and 'p[2]' symbol pins for the capacitor.""" # Floor keeps the pins on the grid. w = math.floor(self.pitch / 2) self.p = { 1: Pin(at=(0, w), direction=Direction.Up), 2: Pin(at=(0, -w), direction=Direction.Down), } def _top_plate( self, width: float, top: float, cross_y: float, line_width: float ) -> tuple[Shape[Polyline], Shape[Polyline]]: """Construct the capacitor porch and plate shapes that make up the top plate.""" w2 = width / 2.0 porch = Polyline( line_width, [(0.0, top), (0.0, cross_y)], ) plate = Polyline( line_width, [ (-w2, cross_y), (w2, cross_y), ], ) return (porch, plate) def _build_capacitor_plates(self) -> None: """Build the capacitor plates (top and bottom).""" h = self.pitch / 2.0 # Resolve porch_width (could be absolute or percentage of pitch/2) actual_porch_width = _resolve_value(self.porch_width, h) # Y point at which the cross bar for the capacitor plate starts cross_y = h - actual_porch_width # Build top plate porch, plate = self._top_plate(self.width, h, cross_y, self.line_width) self.capacitor_top_plate = (porch, plate) # Build bottom plate (rotated 180 degrees) rotate = Transform((0, 0), 180) self.capacitor_bottom_plate = (rotate * porch, rotate * plate) # Convenience properties to access config values @property def pitch(self) -> float: """See :attr:`~.CapacitorConfig.pitch`.""" return self.config.pitch @property def porch_width(self) -> float: """See :attr:`~.CapacitorConfig.porch_width`.""" return self.config.porch_width @property def width(self) -> float: """See :attr:`~.CapacitorConfig.width`.""" return self.config.width @property def line_width(self) -> float: """See :attr:`~.CapacitorConfig.line_width`.""" return self.config.line_width @property def label_config(self) -> LabelConfigurable: """Configuration object that provides label configuration""" return self.config
[docs] @dataclass class PolarizedCapacitorConfig(CapacitorConfig): """ Configuration for polarized capacitor symbols Extends CapacitorConfig with polarized-specific parameters. """ style: PolarizedStyle = DEF_CAP_POL_STYLE """Visual style for the polarized capacitor (straight or curved)""" pol_radius: float = DEF_CAP_POL_RADIUS """Radius for the curved bottom plate in curved style""" plus_size: float = DEF_CAP_PLUS_SIZE """Size of the plus sign indicator (absolute or percentage of width)"""
[docs] class PolarizedCapacitorSymbol[T: PolarizedCapacitorConfig](CapacitorSymbol): """ Polarized capacitor symbol with graphics and pins. Supports both straight and curved bottom plate styles. """ config: T plus_sign: tuple[Shape[Polyline], Shape[Polyline]] | None = None a: Pin c: Pin @override def _symbol_style_config( self, context: SymbolStyleContext | None = None ) -> PolarizedCapacitorConfig: """Symbol style config for this polarized capacitor symbol.""" if context is None: return PolarizedCapacitorConfig() else: return context.polarized_capacitor_config @override def _build_pins(self) -> None: """Build 'a' and 'c' symbol pins for the capacitor.""" # Floor keeps the pins on the grid. w = math.floor(self.pitch / 2) self.a = Pin(at=(0, w), direction=Direction.Up) self.c = Pin(at=(0, -w), direction=Direction.Down) @override def _build_capacitor_plates(self) -> None: """Build the capacitor plates (top and bottom) with polarized styling.""" h = self.pitch / 2.0 # Resolve porch_width (could be absolute or percentage of pitch/2) actual_porch_width = _resolve_value(self.porch_width, h) # Y point at which the cross bar for the capacitor plate starts cross_y = h - actual_porch_width # Build plus sign self._build_plus_sign(cross_y) if self.config.style == PolarizedStyle.STRAIGHT: # Straight style - same as regular capacitor super()._build_capacitor_plates() return elif self.config.style == PolarizedStyle.CURVED: self.capacitor_top_plate = self._top_plate( self.width, h, cross_y, self.line_width ) # Curved style - use arc for bottom plate self.capacitor_bottom_plate = self._curved_bottom_plate(h, cross_y) def _curved_bottom_plate( self, h: float, cross_y: float ) -> tuple[Shape[Polyline], ArcPolyline]: """Construct the porch and curved plate that make up the bottom plate.""" pol_r = self.config.pol_radius w2 = self.width / 2.0 half_angle = math.degrees(math.atan(w2 / pol_r)) # Porch line porch_line = Polyline(self.line_width, [(0.0, -h), (0.0, -cross_y)]) # Create the arc (curved part of the bottom plate) arc_center = (0.0, -(cross_y + pol_r)) arc_polyline = ArcPolyline( self.line_width, [ Arc(arc_center, pol_r, 90.0 - half_angle, 2 * half_angle), ], ) return (porch_line, arc_polyline) def _build_plus_sign(self, cross_y: float) -> None: """Build the plus sign indicator for the polarized capacitor.""" # Resolve plus_size (could be absolute or percentage of width) plus_len = _resolve_value(self.config.plus_size, self.width) arm = plus_len / 2.0 x_pos = (self.width / 2.0) - arm y_pos = cross_y + (2.0 * arm) plus_line1 = Polyline(self.line_width, [(-arm, 0.0), (arm, 0.0)]) plus_line2 = Polyline(self.line_width, [(0.0, -arm), (0.0, arm)]) transform = Transform((x_pos, y_pos)) self.plus_sign = (transform * plus_line1, transform * plus_line2) # Additional convenience properties for polarized capacitor @property def style(self) -> PolarizedStyle: """See :attr:`~.PolarizedCapacitorConfig.style`.""" return self.config.style @property def pol_radius(self) -> float: """See :attr:`~.PolarizedCapacitorConfig.pol_radius`.""" return self.config.pol_radius @property def plus_size(self) -> float: """See :attr:`~.PolarizedCapacitorConfig.plus_size`.""" return self.config.plus_size