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 jitx.anchor import Anchor
from jitx.inspect import extract
from jitx.shapes import Shape
from jitx.shapes.composites import bounds_union
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 = bounds_union(s.to_shapely().bounds for s in shapes)
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,
)