Source code for jitxlib.symbols.label
"""
Labels module for JITX Standard Library
This module provides configurations for reference designator and value labels in symbols
"""
from __future__ import annotations
from dataclasses import dataclass
from functools import reduce
from jitx.anchor import Anchor
from jitx.inspect import extract
from jitx.shapes import Shape
from jitx.shapes.primitive import Polyline, Text
from jitx.symbol import Direction, Pin, Symbol
from jitx.transform import Transform
# LabelConfig constants
DEF_REF_LABEL_SIZE = 1.0
DEF_VALUE_LABEL_SIZE = 1.0
[docs]
@dataclass
class LabelConfig:
"""Configuration for reference desigantor and value labels in symbols"""
ref_size: float = DEF_REF_LABEL_SIZE
"""Reference designator label size"""
value_size: float | None = DEF_VALUE_LABEL_SIZE
"""Value label size, can be set to None to avoid creating a value label"""
def __post_init__(self):
if self.ref_size <= 0:
raise ValueError(
f"Reference designator label size {self.ref_size} must be positive"
)
if self.value_size is not None and self.value_size <= 0:
raise ValueError(
f"Value label size {self.ref_size} must be positive if provided"
)
[docs]
@dataclass
class LabelConfigurable:
"""Label configuration wrapper, useful for handling defaults"""
label_config: LabelConfig | None = None
def __init__(self, label_config: LabelConfig | None = None):
self.label_config = label_config
[docs]
def get_label_config(self) -> LabelConfig:
"""
Returns the label configuration.
If an label config is specified, it will be used.
Otherwise, the config will be pulled from the context.
If no context is available, a default config will be used.
"""
from .context import SymbolStyleContext
context = SymbolStyleContext.get()
if self.label_config is None:
if context is None:
return LabelConfig()
else:
return context.label_config
else:
return self.label_config
[docs]
class LabelledSymbol(Symbol):
"""Base class for symbols with reference designator and/or value labels
Subclasses must have a 'config' property that extends LabelConfigurable to provide
label configuration.
"""
reference: Shape[Text]
value: Shape[Text]
@property
def label_config(self) -> LabelConfigurable:
"""Configuration object that provides label configuration
Subclasses must override this property to provide their configuration.
"""
raise NotImplementedError(
f"{self.__class__.__name__} must implement the 'config' property "
f"returning a LabelConfigurable instance"
)
def _build_labels(
self,
*,
ref: Direction | None = None,
value: Direction | None = None,
margin: float = 0.5,
) -> None:
"""Build reference designator labels for the symbol."""
shapes = list(extract(self, Shape))
for pin in extract(self, Pin):
start = pin.at
if pin.direction == Direction.Up:
end = (start[0], start[1] + pin.length)
elif pin.direction == Direction.Down:
end = (start[0], start[1] - pin.length)
elif pin.direction == Direction.Right:
end = (start[0] + pin.length, start[1])
elif pin.direction == Direction.Left:
end = (start[0] - pin.length, start[1])
else:
raise ValueError(f"Invalid direction: {pin.direction}")
# This width chosen to match the pin width in the schematic visualizer.
# The value really just needs to be a small positive value.
shapes.append(Polyline(0.15 / 1.27, [start, end]))
bounds = reduce(
lambda a, b: a.union(b), [s.to_shapely() for s in shapes]
).bounds
label_config = self.label_config.get_label_config()
ref_size = label_config.ref_size
value_size = label_config.value_size
show_value = value_size is not None
avg_size = (ref_size + value_size) / 2 if show_value else ref_size
if ref:
if ref == Direction.Up:
offset = (0.0, bounds[3] + margin)
anchor = Anchor.S
elif ref == Direction.Down:
offset = (0.0, bounds[1] - margin)
anchor = Anchor.N
elif ref == Direction.Right:
offset = (bounds[2] + margin, 0.0)
anchor = Anchor.W
else:
offset = (bounds[0] - margin, 0.0)
anchor = Anchor.E
if ref == value and show_value:
if ref == Direction.Up:
offset = (offset[0], offset[1] + (value_size or 0))
elif ref == Direction.Down:
pass
else:
offset = (offset[0], offset[1] + avg_size / 2)
self.reference = Transform(offset) * Text(
string=">REF",
size=ref_size,
anchor=anchor,
)
if value and show_value:
if value == Direction.Up:
offset = (0.0, bounds[3] + margin)
anchor = Anchor.S
elif value == Direction.Down:
offset = (0.0, bounds[1] - margin)
anchor = Anchor.N
elif value == Direction.Right:
offset = (bounds[2] + margin, 0.0)
anchor = Anchor.W
else:
offset = (bounds[0] - margin, 0.0)
anchor = Anchor.E
if ref == value:
if ref == Direction.Up:
pass
elif ref == Direction.Down:
offset = (offset[0], offset[1] - ref_size)
else:
offset = (offset[0], offset[1] - avg_size / 2)
self.value = Transform(offset) * Text(
string=">VALUE",
size=value_size,
anchor=anchor,
)