Source code for kicad_sch_api.parsers.elements.label_parser

"""
Label and hierarchical label elements parser for KiCAD schematics.

Handles parsing and serialization of Label and hierarchical label elements.
"""

import logging
from typing import Any, Dict, List, Optional

import sexpdata

from ...core.config import config
from ..base import BaseElementParser

logger = logging.getLogger(__name__)


[docs] class LabelParser(BaseElementParser): """Parser for Label and hierarchical label elements."""
[docs] def __init__(self): """Initialize label parser.""" super().__init__("label")
def _parse_label(self, item: List[Any]) -> Optional[Dict[str, Any]]: """Parse a label definition.""" # Label format: (label "text" (at x y rotation) (effects ...) (uuid ...)) if len(item) < 2: return None label_data = { "text": str(item[1]), # Label text is second element "position": {"x": 0, "y": 0}, "rotation": 0, "size": config.defaults.font_size, "justify_h": "left", "justify_v": "bottom", "uuid": None, } for elem in item[2:]: # Skip label keyword and text if not isinstance(elem, list): continue elem_type = str(elem[0]) if isinstance(elem[0], sexpdata.Symbol) else None if elem_type == "at": # Parse position: (at x y rotation) if len(elem) >= 3: label_data["position"] = {"x": float(elem[1]), "y": float(elem[2])} if len(elem) >= 4: label_data["rotation"] = float(elem[3]) elif elem_type == "effects": # Parse effects for font size and justification: (effects (font (size x y)) (justify left bottom)) for effect_elem in elem[1:]: if isinstance(effect_elem, list): effect_type = ( str(effect_elem[0]) if isinstance(effect_elem[0], sexpdata.Symbol) else None ) if effect_type == "font": # Parse font size for font_elem in effect_elem[1:]: if isinstance(font_elem, list) and str(font_elem[0]) == "size": if len(font_elem) >= 2: label_data["size"] = float(font_elem[1]) elif effect_type == "justify": # Parse justification (e.g., "left bottom", "right top") # Format: (justify left bottom) or (justify right) if len(effect_elem) >= 2: label_data["justify_h"] = str(effect_elem[1]) if len(effect_elem) >= 3: label_data["justify_v"] = str(effect_elem[2]) elif elem_type == "uuid": label_data["uuid"] = str(elem[1]) if len(elem) > 1 else None return label_data def _parse_hierarchical_label(self, item: List[Any]) -> Optional[Dict[str, Any]]: """Parse a hierarchical label definition.""" # Format: (hierarchical_label "text" (shape input) (at x y rotation) (effects ...) (uuid ...)) if len(item) < 2: return None hlabel_data = { "text": str(item[1]), # Hierarchical label text is second element "shape": "input", # input/output/bidirectional/tri_state/passive "position": {"x": 0, "y": 0}, "rotation": 0, "size": config.defaults.font_size, "justify": "left", "uuid": None, } for elem in item[2:]: # Skip hierarchical_label keyword and text if not isinstance(elem, list): continue elem_type = str(elem[0]) if isinstance(elem[0], sexpdata.Symbol) else None if elem_type == "shape": # Parse shape: (shape input) if len(elem) >= 2: hlabel_data["shape"] = str(elem[1]) elif elem_type == "at": # Parse position: (at x y rotation) if len(elem) >= 3: hlabel_data["position"] = {"x": float(elem[1]), "y": float(elem[2])} if len(elem) >= 4: hlabel_data["rotation"] = float(elem[3]) elif elem_type == "effects": # Parse effects for font size and justification: (effects (font (size x y)) (justify left)) for effect_elem in elem[1:]: if isinstance(effect_elem, list): effect_type = ( str(effect_elem[0]) if isinstance(effect_elem[0], sexpdata.Symbol) else None ) if effect_type == "font": # Parse font size for font_elem in effect_elem[1:]: if isinstance(font_elem, list) and str(font_elem[0]) == "size": if len(font_elem) >= 2: hlabel_data["size"] = float(font_elem[1]) elif effect_type == "justify": # Parse justification (e.g., "left", "right") if len(effect_elem) >= 2: hlabel_data["justify"] = str(effect_elem[1]) elif elem_type == "uuid": hlabel_data["uuid"] = str(elem[1]) if len(elem) > 1 else None return hlabel_data def _label_to_sexp(self, label_data: Dict[str, Any]) -> List[Any]: """Convert local label to S-expression.""" sexp = [sexpdata.Symbol("label"), label_data["text"]] # Add position pos = label_data["position"] x, y = pos["x"], pos["y"] rotation = label_data.get("rotation", 0) # Format coordinates properly if isinstance(x, float) and x.is_integer(): x = int(x) if isinstance(y, float) and y.is_integer(): y = int(y) sexp.append([sexpdata.Symbol("at"), x, y, rotation]) # Add effects (font properties and justification) size = label_data.get("size", config.defaults.font_size) effects = [sexpdata.Symbol("effects")] font = [sexpdata.Symbol("font"), [sexpdata.Symbol("size"), size, size]] effects.append(font) # Use justification from data, defaulting to "left bottom" justify_h = label_data.get("justify_h", "left") justify_v = label_data.get("justify_v", "bottom") effects.append( [sexpdata.Symbol("justify"), sexpdata.Symbol(justify_h), sexpdata.Symbol(justify_v)] ) sexp.append(effects) # Add UUID if "uuid" in label_data: sexp.append([sexpdata.Symbol("uuid"), label_data["uuid"]]) return sexp def _hierarchical_label_to_sexp(self, hlabel_data: Dict[str, Any]) -> List[Any]: """Convert hierarchical label to S-expression.""" sexp = [sexpdata.Symbol("hierarchical_label"), hlabel_data["text"]] # Add shape shape = hlabel_data.get("shape", "input") sexp.append([sexpdata.Symbol("shape"), sexpdata.Symbol(shape)]) # Add position pos = hlabel_data["position"] x, y = pos["x"], pos["y"] rotation = hlabel_data.get("rotation", 0) sexp.append([sexpdata.Symbol("at"), x, y, rotation]) # Add effects (font properties) size = hlabel_data.get("size", config.defaults.font_size) effects = [sexpdata.Symbol("effects")] font = [sexpdata.Symbol("font"), [sexpdata.Symbol("size"), size, size]] effects.append(font) # Use justification from data if provided, otherwise default to "left" justify = hlabel_data.get("justify", "left") effects.append([sexpdata.Symbol("justify"), sexpdata.Symbol(justify)]) sexp.append(effects) # Add UUID if "uuid" in hlabel_data: sexp.append([sexpdata.Symbol("uuid"), hlabel_data["uuid"]]) return sexp