Source code for kicad_sch_api.parsers.elements.wire_parser

"""
Wire and connection element parsers for KiCAD schematics.

Handles parsing and serialization of connection elements:
- Wire
- Junction
- No-connect
"""

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 WireParser(BaseElementParser): """Parser for wire and connection elements."""
[docs] def __init__(self): """Initialize wire parser.""" super().__init__("wire")
def _parse_wire(self, item: List[Any]) -> Optional[Dict[str, Any]]: """Parse a wire definition.""" wire_data = { "points": [], "stroke_width": 0.0, "stroke_type": config.defaults.stroke_type, "uuid": None, "wire_type": "wire", # Default to wire (vs bus) } for elem in item[1:]: if not isinstance(elem, list): continue elem_type = str(elem[0]) if isinstance(elem[0], sexpdata.Symbol) else None if elem_type == "pts": # Parse points: (pts (xy x1 y1) (xy x2 y2) ...) for pt in elem[1:]: if isinstance(pt, list) and len(pt) >= 3: if str(pt[0]) == "xy": x, y = float(pt[1]), float(pt[2]) wire_data["points"].append({"x": x, "y": y}) elif elem_type == "stroke": # Parse stroke: (stroke (width 0) (type default)) for stroke_elem in elem[1:]: if isinstance(stroke_elem, list) and len(stroke_elem) >= 2: stroke_type = str(stroke_elem[0]) if stroke_type == "width": wire_data["stroke_width"] = float(stroke_elem[1]) elif stroke_type == "type": wire_data["stroke_type"] = str(stroke_elem[1]) elif elem_type == "uuid": wire_data["uuid"] = str(elem[1]) if len(elem) > 1 else None # Only return wire if it has at least 2 points if len(wire_data["points"]) >= 2: return wire_data else: logger.warning(f"Wire has insufficient points: {len(wire_data['points'])}") return None def _parse_junction(self, item: List[Any]) -> Optional[Dict[str, Any]]: """Parse a junction definition.""" junction_data = { "position": {"x": 0, "y": 0}, "diameter": 0, "color": (0, 0, 0, 0), "uuid": None, } for elem in item[1:]: 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) if len(elem) >= 3: junction_data["position"] = {"x": float(elem[1]), "y": float(elem[2])} elif elem_type == "diameter": # Parse diameter: (diameter value) if len(elem) >= 2: junction_data["diameter"] = float(elem[1]) elif elem_type == "color": # Parse color: (color r g b a) if len(elem) >= 5: junction_data["color"] = ( int(elem[1]), int(elem[2]), int(elem[3]), int(elem[4]), ) elif elem_type == "uuid": junction_data["uuid"] = str(elem[1]) if len(elem) > 1 else None return junction_data def _parse_no_connect(self, item: List[Any]) -> Optional[Dict[str, Any]]: """Parse a no_connect symbol.""" # Format: (no_connect (at x y) (uuid ...)) no_connect_data = {"position": {"x": 0, "y": 0}, "uuid": None} for elem in item[1:]: if not isinstance(elem, list): continue elem_type = str(elem[0]) if isinstance(elem[0], sexpdata.Symbol) else None if elem_type == "at": if len(elem) >= 3: no_connect_data["position"] = {"x": float(elem[1]), "y": float(elem[2])} elif elem_type == "uuid": no_connect_data["uuid"] = str(elem[1]) if len(elem) > 1 else None return no_connect_data def _wire_to_sexp(self, wire_data: Dict[str, Any]) -> List[Any]: """Convert wire to S-expression.""" sexp = [sexpdata.Symbol("wire")] # Add points (pts section) points = wire_data.get("points", []) if len(points) >= 2: pts_sexp = [sexpdata.Symbol("pts")] for point in points: if isinstance(point, dict): x, y = point["x"], point["y"] elif isinstance(point, (list, tuple)) and len(point) >= 2: x, y = point[0], point[1] else: # Assume it's a Point object x, y = point.x, point.y # Format coordinates properly (avoid unnecessary .0 for integers) if isinstance(x, float) and x.is_integer(): x = int(x) if isinstance(y, float) and y.is_integer(): y = int(y) pts_sexp.append([sexpdata.Symbol("xy"), x, y]) sexp.append(pts_sexp) # Add stroke information stroke_width = wire_data.get("stroke_width", config.defaults.stroke_width) stroke_type = wire_data.get("stroke_type", config.defaults.stroke_type) stroke_sexp = [sexpdata.Symbol("stroke")] # Format stroke width (use int for 0, preserve float for others) if isinstance(stroke_width, float) and stroke_width == 0.0: stroke_width = 0 stroke_sexp.append([sexpdata.Symbol("width"), stroke_width]) stroke_sexp.append([sexpdata.Symbol("type"), sexpdata.Symbol(stroke_type)]) sexp.append(stroke_sexp) # Add UUID if "uuid" in wire_data: sexp.append([sexpdata.Symbol("uuid"), wire_data["uuid"]]) return sexp def _junction_to_sexp(self, junction_data: Dict[str, Any]) -> List[Any]: """Convert junction to S-expression.""" sexp = [sexpdata.Symbol("junction")] # Add position pos = junction_data["position"] if isinstance(pos, dict): x, y = pos["x"], pos["y"] elif isinstance(pos, (list, tuple)) and len(pos) >= 2: x, y = pos[0], pos[1] else: # Assume it's a Point object x, y = pos.x, pos.y # 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]) # Add diameter diameter = junction_data.get("diameter", 0) sexp.append([sexpdata.Symbol("diameter"), diameter]) # Add color (RGBA) color = junction_data.get("color", (0, 0, 0, 0)) if isinstance(color, (list, tuple)) and len(color) >= 4: sexp.append([sexpdata.Symbol("color"), color[0], color[1], color[2], color[3]]) else: sexp.append([sexpdata.Symbol("color"), 0, 0, 0, 0]) # Add UUID if "uuid" in junction_data: sexp.append([sexpdata.Symbol("uuid"), junction_data["uuid"]]) return sexp def _no_connect_to_sexp(self, no_connect_data: Dict[str, Any]) -> List[Any]: """Convert no_connect to S-expression.""" sexp = [sexpdata.Symbol("no_connect")] # Add position pos = no_connect_data["position"] x, y = pos["x"], pos["y"] # 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]) # Add UUID if "uuid" in no_connect_data: sexp.append([sexpdata.Symbol("uuid"), no_connect_data["uuid"]]) return sexp