"""
Basic resistor symbol for JITX Standard Library
This module provides the basic resistor symbol definition that serves
as the foundation for all resistor symbol types.
"""
from __future__ import annotations
import math
from enum import Enum
from dataclasses import dataclass, replace
from typing import TYPE_CHECKING, cast
from jitx.shapes import Shape
from jitx.shapes.primitive import Polyline
from jitx.symbol import Direction, Pin
from jitxlib.geometry.linerectangle import line_rectangle
from ..common import DEF_LINE_WIDTH
from ..label import LabelConfigurable, LabelledSymbol
if TYPE_CHECKING:
from ..context import SymbolStyleContext
[docs]
class ResistorStyle(Enum):
"""
Resistor symbol styles
Defines the visual representation style for resistor symbols.
"""
TRIANGLE_WAVE = "triangle_wave" # American zigzag style
OPEN_RECTANGLE = "open_rectangle" # European rectangular style
# ResistorConfig constants
DEF_RES_PITCH = 4.0
DEF_RES_PORCH_WIDTH = 0.5
DEF_RES_AMPLITUDE = 0.5
DEF_RES_PERIODS = 3.0
[docs]
@dataclass
class ResistorConfig(LabelConfigurable):
"""
Configuration for resistor symbols
Defines the geometric and visual parameters for resistor symbols.
"""
pitch: float = DEF_RES_PITCH
"""Distance between pin points"""
porch_width: float = DEF_RES_PORCH_WIDTH
"""Length of straight sections at ends"""
amplitude: float = DEF_RES_AMPLITUDE
"""Height/width of the resistor body"""
periods: float = DEF_RES_PERIODS
"""Number of zigzag periods for triangle wave style"""
line_width: float = DEF_LINE_WIDTH
"""Width of the resistor lines"""
[docs]
class ResistorSymbol[T: ResistorConfig](LabelledSymbol):
"""
Resistor symbol with graphics and pins.
Also serves as the base class for all other resistor symbol types.
"""
config: T
resistor_body: Shape[Polyline]
resistor_porches: tuple[Shape[Polyline], Shape[Polyline]]
def _symbol_style_config(self, context: SymbolStyleContext | None = None) -> T:
"""Symbol style config for this resistor symbol."""
if context is None:
config = ResistorConfig()
else:
config = context.resistor_config
# Casting necessary because ResistorSymbol 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 capacitor class).
def __init__(self, config: T | None = None, **kwargs):
"""
Initialize resistor 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)
# By default, use TRIANGLE_WAVE resistor style.
if context is None:
style = ResistorStyle.TRIANGLE_WAVE
else:
style = context.resistor_style
if style == ResistorStyle.TRIANGLE_WAVE:
self._build_triangle_wave_glyphs()
elif style == ResistorStyle.OPEN_RECTANGLE:
self._build_open_rectangle_glyphs()
else:
raise ValueError(f"Invalid resistor style: {style}")
self._build_pins()
self._build_artwork()
self._build_labels(ref=Direction.Right, value=Direction.Right)
def _build_artwork(self) -> None:
"""Build the artwork for the resistor symbol, apart from the zigzag or rectangle."""
pass
def _build_pins(self) -> None:
"""Build 'p[1]' and 'p[2]' symbol pins for the resistor."""
# 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 _build_triangle_wave_glyphs(self) -> None:
"""Build triangle wave glyphs for a zigzag resistor."""
w = math.floor(self.pitch / 2)
tri_start = w - self.porch_width
total_w = 2 * tri_start
period_w = total_w / self.periods
x_lookup = [0, self.amplitude, 0, -self.amplitude]
points = []
points.append((0, -tri_start))
quarter_periods = int(2 * math.ceil(self.periods / 0.5))
for i in range(quarter_periods):
y = i * (period_w / 4) - tri_start
x = x_lookup[i % 4]
points.append((x, y))
points.append((0, tri_start))
self.resistor_body = Polyline(self.line_width, points)
self.resistor_porches = (
Polyline(self.line_width, [(0, -w), points[0]]),
Polyline(self.line_width, [points[-1], (0, w)]),
)
def _build_open_rectangle_glyphs(self) -> None:
"""Build open rectangle glyphs for a rectangular resistor."""
w = math.floor(self.pitch / 2)
tri_start = w - self.porch_width
total_w = 2 * tri_start
self.resistor_body = line_rectangle(
2 * self.amplitude, total_w, self.line_width
)
self.resistor_porches = (
Polyline(self.line_width, [(0, -w), (0, -tri_start)]),
Polyline(self.line_width, [(0, tri_start), (0, w)]),
)
# Convenience properties to access config values
@property
def pitch(self) -> float:
"""See :attr:`~.ResistorConfig.pitch`."""
return self.config.pitch
@property
def porch_width(self) -> float:
"""See :attr:`~.ResistorConfig.porch_width`."""
return self.config.porch_width
@property
def amplitude(self) -> float:
"""See :attr:`~.ResistorConfig.amplitude`."""
return self.config.amplitude
@property
def periods(self) -> float:
"""See :attr:`~.ResistorConfig.periods`."""
return self.config.periods
@property
def line_width(self) -> float:
"""See :attr:`~.ResistorConfig.line_width`."""
return self.config.line_width
@property
def label_config(self) -> LabelConfigurable:
"""Configuration object that provides label configuration"""
return self.config