skidl.tools.inject_labels module

Post-process a KiCad 8/9 schematic (.kicad_sch) generated by SKiDL to add global net labels at each pin position.

SKiDL’s generate_schematic() produces hierarchical schematics with correct symbols and netlists, but without visible net labels on the pins. This module reads the companion .net netlist, resolves pin-to-net mappings, and injects (global_label ...) S-expressions at the world coordinates of every connected pin.

Features:
  • Parses KiCad S-expression schematics and netlists (no external deps)

  • Handles symbol rotation, mirror, and multi-unit sub-symbols

  • Deduplicates labels at identical positions (e.g. stacked USB-C GND pins)

  • Orients labels to point away from the symbol body

  • Converts auto-generated net names (N$1, Net-(R1-1)) to readable form

Usage as CLI:

python -m skidl.tools.inject_labels schematic.kicad_sch netlist.net output.kicad_sch

Usage from Python:

from skidl.tools.inject_labels import inject_labels
inject_labels("power1.kicad_sch", "circuit.net", "power1_labeled.kicad_sch")
skidl.tools.inject_labels.extract_instances(tree: list) list[dict][source]

Extract all (symbol (lib_id ...) (at SX SY SROT) ...) instances.

Returns a list of dicts with keys: lib_id, ref, sx, sy, rotation, mirror_x, mirror_y.

skidl.tools.inject_labels.extract_lib_pins(tree: list) dict[str, list[dict]][source]

Parse the lib_symbols section of the schematic.

Returns {lib_id: [pin_dict, ...]} where each pin_dict has keys: number, name, x, y, angle.

Pins live in sub-symbols named SYMBOLNAME_N_1 (unit sub-symbols). We collect pins from all unit sub-symbols.

skidl.tools.inject_labels.inject_labels(sch_path: str, net_path: str, out_path: str) int[source]

Read schematic + netlist, inject global labels at pin positions.

Parameters:
  • sch_path – Path to a .kicad_sch file (input).

  • net_path – Path to a .net netlist file.

  • out_path – Path for the labeled .kicad_sch file (output).

Returns:

Number of labels injected.

skidl.tools.inject_labels.is_unnamed_net(name: str) bool[source]

Return True if the net name looks auto-generated / unnamed.

skidl.tools.inject_labels.label_angle_from_position(sx: float, sy: float, wx: float, wy: float) float[source]

Compute label angle so it extends AWAY from the symbol body.

Returns one of 0, 90, 180, 270 (KiCad label angle convention).

skidl.tools.inject_labels.main() None[source]
skidl.tools.inject_labels.make_global_label_sexp(net_name: str, x: float, y: float, angle: float) str[source]

Return a KiCad 8/9 (global_label ...) S-expression string.

skidl.tools.inject_labels.parse_netlist(path: str) dict[tuple[str, str], str][source]

Return a mapping of (component_ref, pin_number) -> net_name from a KiCad .net netlist file.

skidl.tools.inject_labels.parse_sexp(text: str) list[source]

Parse a KiCad S-expression string into nested Python lists.

Atoms are kept as strings. Quoted strings have their quotes stripped. Numbers are NOT converted – everything stays as str so we can round-trip without floating-point surprises.

skidl.tools.inject_labels.pin_world_pos(sx: float, sy: float, rotation: float, mirror_x: bool, mirror_y: bool, px: float, py: float) tuple[float, float][source]

Transform a pin’s local offset to world coordinates.

Accounts for symbol placement, rotation (degrees), and mirror flags. KiCad Y-down coordinate system: positive rotation is clockwise.

skidl.tools.inject_labels.readable_net_name(name: str) str[source]

Convert an auto-generated net name to something readable.

N$1net1, Net-(R1-1)R1_1.