"""
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