"""
Schematic Symbols
=================
This module provides classes for defining schematic symbols, pins,
and symbol mappings for components. You attach a symbol to a component by
assigning it to a member field. The name of the field is not important, and
multiple symbols can be attached to the same component to provide multiple
symbol units.
"""
from __future__ import annotations
from collections.abc import Iterable, Mapping, Sequence
from enum import Enum
from .memo import memoize
from .net import Port
from ._structural import Ref, Structural
from .transform import GridPoint
[docs]
@memoize
class Symbol(Structural):
"""Symbols are a structural element in JITX encoding the schematic
representation of a part. Symbols often contain electrical connection points
represented by :py:class:`~jitx.symbol.Pin`, geometric artwork, and
:py:class:`~jitx.shapes.primitive.Text` annotations. Symbols are linked to a
design through a :py:class:`~jitx.component.Component` where a symbol's
:py:class:`~jitx.symbol.Pin` objects are mapped to :py:class:`~jitx.net.Port` objects.
Valid elements in a symbol are:
- :py:class:`~jitx.shapes.Shape`
- :py:class:`~jitx.symbol.Pin`
- :py:class:`~jitx.symbol.SymbolOrientation`
- :py:class:`~jitx.symbol.Symbol`
If a symbol is defined inside another symbol, they will be treated
together as one symbol in the design.
For a rectangular symbol with automatic or configurable pin layout,
use :py:class:`~jitxlib.symbols.box.BoxSymbol`.
>>> class MySymbol(Symbol):
... a = Pin(at=(1, 0), length=1, direction=Direction.Right)
... b = Pin(at=(-1, 0), length=1, direction=Direction.Left)
... pin_name_size = 0.3
... pad_name_size = 0.3
... orientation = SymbolOrientation(0)
... rect = rectangle(2, 2)
>>> class MyComponent(Component):
... ports = [Port() for _ in range(2)]
... symbol = BoxSymbol()
Note:
Grid units are abstract schematic units where 1 grid unit equals
the standard pin spacing in the schematic editor.
"""
pin_name_size: float | None = None
"""
Font size of pin name text of :py:class:`~jitx.symbol.Pin` objects in this symbol, in grid units.
If unset, defers to a parent :py:class:`~jitx.symbol.Symbol`, if a parent exists.
This can be overriden at the :py:class:`~jitx.symbol.Pin` level by setting its :py:attr:`~jitx.symbol.Symbol.pin_name_size` attribute.
"""
pad_name_size: float | None = None
"""
Font size of pad name text of :py:class:`~jitx.symbol.Pin` objects in this symbol, in grid units.
If unset, defers to a parent :py:class:`~jitx.symbol.Symbol`, if a parent exists.
This can be overriden at the :py:class:`~jitx.symbol.Pin` level by setting its :py:attr:`~jitx.symbol.Symbol.pad_name_size` attribute.
"""
[docs]
class Direction(Enum):
"""Pin direction on schematic symbols."""
Left = "left"
Right = "right"
Up = "up"
Down = "down"
[docs]
class Pin(Structural):
"""Schematic symbol pin definition."""
at: GridPoint
"The position of the pin in grid units."
length: int = 0
"The length of the pin in grid units."
direction: Direction = Direction.Left
"The direction of the pin."
pin_name_size: float | None = None
"""
Font size of the pin's pin name text in grid units, overriding the parent
:py:class:`~jitx.symbol.Symbol`'s :py:attr:`~jitx.symbol.Symbol.pin_name_size` attribute.
"""
pad_name_size: float | None = None
"""
Font size of the pin's pad name text in grid units, overriding the parent
:py:class:`~jitx.symbol.Symbol`'s :py:attr:`~jitx.symbol.Symbol.pad_name_size` attribute.
"""
def __init__(
self,
at: GridPoint,
length: int = 0,
direction: Direction = Direction.Left,
pin_name_size: float | None = None,
pad_name_size: float | None = None,
):
"""Initialize a schematic pin.
Args:
at: Position of the pin in grid units.
length: Length of the pin in grid units.
direction: Direction the pin points.
pin_name_size: Font size for pin name text.
pad_name_size: Font size for pad name text.
"""
self.at = at
self.length = length
self.direction = direction
self.pin_name_size = pin_name_size
self.pad_name_size = pad_name_size
[docs]
@classmethod
def left(
cls,
at: GridPoint,
length: int = 0,
pin_name_size: float | None = None,
pad_name_size: float | None = None,
):
"""Create a left-facing pin (wire extends to the left).
Args:
at: Grid position (row, column) for the pin.
length: Length of the pin wire in grid units.
pin_name_size: Optional font size for pin name label.
pad_name_size: Optional font size for pad name label.
Returns:
A Pin instance configured to face left.
>>> Pin.left((0, 0), length=2) # Pin at row 0, col 0
"""
return cls(at, length, Direction.Left, pin_name_size, pad_name_size)
[docs]
@classmethod
def right(
cls,
at: GridPoint,
length: int = 0,
pin_name_size: float | None = None,
pad_name_size: float | None = None,
):
"""Create a right-facing pin (wire extends to the right).
Args:
at: Grid position (row, column) for the pin.
length: Length of the pin wire in grid units.
pin_name_size: Optional font size for pin name label.
pad_name_size: Optional font size for pad name label.
Returns:
A Pin instance configured to face right.
>>> Pin.right((0, 5), length=2) # Pin at row 0, col 5
"""
return cls(at, length, Direction.Right, pin_name_size, pad_name_size)
[docs]
@classmethod
def up(
cls,
at: GridPoint,
length: int = 0,
pin_name_size: float | None = None,
pad_name_size: float | None = None,
):
"""Create an up-facing pin (wire extends upward).
Args:
at: Grid position (row, column) for the pin.
length: Length of the pin wire in grid units.
pin_name_size: Optional font size for pin name label.
pad_name_size: Optional font size for pad name label.
Returns:
A Pin instance configured to face up.
>>> Pin.up((5, 2), length=1) # Pin at row 5, col 2
"""
return cls(at, length, Direction.Up, pin_name_size, pad_name_size)
[docs]
@classmethod
def down(
cls,
at: GridPoint,
length: int = 0,
pin_name_size: float | None = None,
pad_name_size: float | None = None,
):
"""Create a down-facing pin (wire extends downward).
Args:
at: Grid position (row, column) for the pin.
length: Length of the pin wire in grid units.
pin_name_size: Optional font size for pin name label.
pad_name_size: Optional font size for pad name label.
Returns:
A Pin instance configured to face down.
>>> Pin.down((0, 2), length=1) # Pin at row 0, col 2
"""
return cls(at, length, Direction.Down, pin_name_size, pad_name_size)
def __repr__(self):
return f"Pin(at={self.at}, length={self.length}, direction={self.direction})"
[docs]
class SymbolMapping(Structural, Ref):
"""Mapping between component ports and schematic symbol pins.
If no symbol mapping is provided, a default mapping will be created that maps ports to symbol pins in declaration order.
>>> class MyComponent(Component):
... GND = Power()
... VIN = Power()
... VOUT = Power()
...
... symbol = MySymbol()
... mappings = SymbolMapping({
... GND: symbol.gnd,
... VIN: symbol.vin,
... VOUT: symbol.vout,
... })
"""
__entries: dict[Port, Pin]
def __init__(self, entries: Mapping[Port, Pin] | Iterable[tuple[Port, Pin]]):
"""Initialize a symbol mapping.
Args:
entries: Mapping or iterable of (port, pin) pairs.
"""
self.__entries = dict(entries)
def __setitem__(self, port: Port, pin: Pin):
self.__entries[port] = pin
def __getitem__(self, port: Port):
return self.__entries[port]
def __iter__(self):
return iter(self.__entries)
[docs]
def items(self):
return self.__entries.items()
[docs]
def values(self):
return self.__entries.values()
[docs]
class SymbolOrientation:
"""Permitted orientations for a schematic symbol.
In initial placement of schematic symbols, the engine may rotate the symbol
to be in any of the permitted orientations. If no orientations are specified,
then all orientations are permitted.
"""
rotations: list[int]
"""List of allowed rotation angles in degrees."""
def __init__(self, rotations: Sequence[int] | int = ()):
"""Initialize symbol orientation constraints.
Args:
rotations: Allowed rotation angles in degrees (must be multiples of 90).
Raises:
ValueError: If rotation is not a multiple of 90 degrees.
"""
if isinstance(rotations, int):
rotations = [rotations]
# Normalize rotations to 0-359 range and validate they're multiples of 90
normalized_rotations = []
for r in rotations:
normalized = r % 360
if normalized % 90 != 0:
raise ValueError(
f"Invalid symbol orientation: {r}. Rotation must be a multiple of 90 degrees"
)
normalized_rotations.append(normalized)
# Use sorted set to automatically handle ordering and deduplication
self.rotations = sorted(set(normalized_rotations))
# no export
del Structural
# ruff doesn't like deleting things that weren't used as base classes?
# del GridPoint