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, )