← Documentation index

PPRESOLVE_COMPONENTS

NAME
SYNOPSIS
DESCRIPTION
OPTIONS
FILES
The --sources file
ENVIRONMENT
EXIT STATUS
EXAMPLES
THE BOARD
PIN IDENTITIES AND THE PIN MAP
SEE ALSO
AUTHOR
COPYRIGHT

NAME

ppresolve_components - build a working KiCad schematic from build instructions

SYNOPSIS

ppresolve_components --sources FILE -o FILE [--config FILE] [--kicad-cli CMD] [--kicad-version N] [--cache-dir DIR] [--title TITLE] [--date DATE] [--rev REV] [--company NAME] [--anchor-spacing MM] [--row-gap MM] [--row-spacing MM] [--paper SIZE] [--debug] [--quiet] INSTRUCTIONS-FILE
ppresolve_components
{--help|--version}

DESCRIPTION

ppresolve_components reads a real build-instruction text file, as produced by ppbuild_instructions(1), and writes a real, working KiCad schematic (.kicad_sch) built from it — the instructions ⇒ KiCad half of this project’s own complete loop (ppbuild_instructions(1) itself, together with pprender(1) and ppauto_layout(1), already covers the other half: KiCad ⇒ instructions).

Internally this program runs three real steps in a fixed, always- together sequence: parse_instructions.py ’s own reconstruct(), recovering a real netlist and placement from the plain-English text alone; resolve_components.py ’s own resolve_components() and resolve_pin_identities(), resolving every component against real, external component data (see --sources below — the instructions themselves only ever carry a component’s own human-readable value, never its real library/part identity); and kicad_layout.py ’s own generate_schematic_auto(), which lays out and writes the real schematic itself, retrying with progressively larger paper sizes until the design genuinely fits.

A component the reconstructed instructions never had reason to state a full connection for (an anchor’s own rail membership, for instance — see ResearchJournal.org ’s own account of this project’s own, deliberately deferred "Option A" gap) is written to the schematic with only the connections the instructions genuinely describe; nothing is invented to fill a real gap silently.

A part whose symbol has several units (a dual op-amp) is laid out one unit at a time, as ppkicad_layout(1) does: each unit the design uses is placed as its own symbol, with its own pins, under the part’s real reference. Nets that share a physical pin (a pin tied to a rail and also stated by number in the instructions) are merged into one; two nets that both carry a real name on one pin are refused, naming both, as that is a wrong pin_map, not something to repair (a power rail and the circuit’s own name for the same node, such as VBUS and VCC, are one net). Any refusal (two labels on one point, a drawing that would join two different nets) is reported as a message and exit status 1.

The output is reproducible: the same input, the same sources file, the same options and the same title-block date give the same files, byte for byte, whatever the Python hash seed. The identifiers of the items of the schematic are made from the circuit that the instructions and the sources file describe, its parts and its nets, and from nothing else (not from the title, not from any date, not from the way the files are written), and not drawn at random, and KiCad, which writes the items of a file in the order of their identifiers, therefore writes them in the same order. The date is today’s unless --date gives one, so that a file made on another day differs in its title block; give --date to get the same bytes on any day.

OPTIONS

--sources

(Required.) A real, JSON file mapping each component’s own reference designator to the real component data needed to resolve it, and, optionally, real anchor pin identities. See FILES below for the exact, real schema.

--config

A real ppbreadboard_config(1) config file describing the target board — the SAME real file ppbuild_instructions(1) accepts, checked next to INSTRUCTIONS-FILE instead of a schematic (same resolution otherwise). Only this real config’s own rows_per_half field is ever actually used, by reconstruct()’s own real hole decoding — CORRECTED 2026-09-21: without this, instructions generated against a non-default board silently, genuinely misdecode, since a hole name’s own row letter is only ever meaningful relative to the SAME real rows_per_half the instructions were generated with. Without this option, the same default lookup pprender(1) uses applies. Since 0.72.82 the config is also CHECKED against the board or boards the instructions state about themselves (their Board: or Boards: line): the number of boards and, for each, its columns (stated only for several boards), rows per half and sectors. A contradiction is an error that names every difference, since the hole names would be read wrongly; a text that states nothing (the default board) is read with the config given. The config is the input of the layout and the line in the text is what the generator stated it used, so they differ only when a config is given to read a text with, or the text is edited.

-o

(Required.) Output path for the generated .kicad_sch file. A matching .kicad_pro project file is written alongside it, same base name. A circuit above the per-sheet limit (see --max-components) is written as a root sheet at this path plus child sheets beside it, named BASE-sheetN.kicad_sch; the project file belongs to the root. Every file written is reported.

--kicad-cli

The kicad-cli(1) command to use, needed for a --sources entry of kind lcsc and, unless --kicad-version is 7, to convert the files written. Default: the KICAD_CLI environment variable if set, else kicad-cli on PATH.

--kicad-version N

The KiCad major version of the files written. Default: the value of the environment variable PACKPAY_KICAD_VERSION if set, else 10. Version 7 is written by this tool itself; any other is the format of the installed kicad-cli, to which every file is converted (one kicad-cli sch upgrade call per file). A version that cannot be written is refused before anything is done. See ppkicad_layout(1).

--cache-dir

Where resolved or generated .kicad_sym files are written during resolution. Default: a real, fresh temporary directory, created for this run and discarded on exit. Pass a real, persistent directory to inspect what was resolved, or to reuse it across repeated runs against the same sources.

--title

Schematic title-block title. Default: the instructions file’s own base name, extension stripped.

--date

Schematic title-block date. Default: today, ISO 8601 (YYYY-MM-DD).

--rev

Schematic title-block revision. Default: empty.

--company

Schematic title-block company name. Default: empty.

--anchor-spacing

The MINIMUM horizontal spacing, in mm, between anchor components. Default: 48; widened automatically for a large anchor so that neighbours never overlap (see kicad_layout.py ’s own generate_schematic() docstring for how this interacts with each anchor’s own real, resolved footprint width).

--row-gap

The MINIMUM vertical gap, in mm, between the anchor row and the dedicated row rail-only passives (e.g. decoupling capacitors) are placed in, and between wrapped rows. Default: 15; raised automatically where the parts’ own height plus the two 5.08 mm label stubs at their ends need more. Increase this if generation fails with an error reporting two labels colliding at the exact same point and naming the dedicated row.

--row-spacing

Horizontal spacing, in mm, between components within the dedicated row. Default: 20.

--paper

The paper size to try first, one of A4, A3, A2, A1 and A0 (default: A4). Each larger size is still tried, in order, if the design does not fit, exactly as ppkicad_layout(1) does with its option of the same name. A design that fits is written on the size asked for, however small.

--max-components

Override this project’s own real, default component-count limit ( breadboard_config.py’s own DEFAULT_MAX_COMPONENTS constant, currently 150) — see that constant’s own docstring for the full, real rationale. Here it is the per-sheet limit: a circuit above it is not refused but split across a root sheet and child sheets (hierarchical labels and sheet pins, net names kept exact), each sheet within the limit. Raising it above the real, existing default prints a real, direct warning to standard error, but still proceeds (one larger sheet).

--version

Show this project’s own current version (read from VERSION) and exit.

--debug

Also print every paper-size retry tier generate_schematic_auto() attempts, to standard error, as each one is tried.

--quiet

Suppress the two "wrote messages (schematic and project file) printed to standard error on success.

FILES

The --sources file

A real JSON object with two, real, top-level keys, both optional (though an entry missing for a real component that needs one makes resolution fail for that component specifically — see EXIT STATUS below):
sources

An object keyed by reference designator. Each value is itself an object with a real kind field selecting which of three real, supported resolution paths applies, plus that kind’s own, further, real fields:
"kind":"kicad"

A component already in an installed, real KiCad symbol library. Further real fields: lib, part, sym_path (the real .kicad_sym file’s own path).

"kind":"eagle"

A component whose own real pin geometry is only available as an Eagle-format (.sch) design file — common for suppliers who never published KiCad- native data at all. Further real fields: sch_path (the real .sch file’s own path), symbol (the symbol’s own real name inside that file).

"kind":"lcsc"

A component to fetch live from LCSC/EasyEDA via easyeda2kicad (pip install easyeda2kicad), which must be installed and on PATH; without it the part is reported as "lcsc import failed -- cannot run easyeda2kicad", with the part’s reference, and the exit status is 1. Further real field: lcsc_id.

pin_map

An object keyed by reference designator, each value itself an object mapping a real, reconstructed net name to that component’s own real, resolved pin id (number or name, whichever its own real symbol uses). The value may also be an ARRAY of pin ids, for an anchor with several pins on one net: an IC with two ground pins, or a dual transistor with two supply pins, has ONE rail connection in the instructions, and every pin listed joins that net. Needed only for an ANCHOR component whose own bus pins (SPI/I2C and similar) the instructions never had reason to number explicitly — see resolve_pin_identities() ’s own docstring for the full, real account of this second, separate resolution gap. Not needed for a part whose step names its pins: an anchor’s pins, a rail-direct source’s pins on each rail and, since 0.72.60, the pins of a two-lead part whose default numbering would be wrong (an audio jack’s S and T). camera-trap, solar-roller, the altimeter and knight-roller come back from their own instructions with an EMPTY pin_map; an instruction text that predates those sentences still needs one for those parts.

The key of a net is its name in the instructions. A net that the instructions call GND or VBUS without its being the ground or power rail (a USB 5 V net called VBUS beside a 3.3 V power rail) has the key GND#net or VBUS#net, since the rails themselves are GND and VBUS in the rebuilt circuit.

The paths of a sources file (sym_path and sch_path) are of three kinds: an absolute path, used as it is; a path with environment variables, such as ${KICAD_SYM_DIR}/Device.kicad_sym (KICAD_SYM_DIR and KICAD_FOOTPRINT_DIR default to KiCad’s own directories, /usr/share/kicad/symbols and /usr/share/kicad/footprints); and a relative path, which is looked for in the component library directories, those of PACKPAY_COMPONENT_LIBS or, when it is not set, ˜/.local/share/packpay/component_libs, and then relative to the current directory. A sources file that is shared should use only the last two kinds, and a relative path in it does not include the name of the library directory. A file that is not found stops the run with a message that names what was looked for, and where.

Worked example (the real altimeter circuit’s own, real --sources file):

{
"sources": {
"U1": {"kind": "kicad", "lib": "Seeed_Studio_XIAO_Series",
"part": "XIAO-SAMD21-SMD",
"sym_path": "Seeed_Studio_XIAO_Series.kicad_sym"},
"U2": {"kind": "eagle",
"sch_path": "Adafruit-BMP280-Breakout-PCB/Adafruit BMP280.sch",
"symbol": "BME280"},
"BT1": {"kind": "kicad", "lib": "Device", "part": "Battery_Cell",
"sym_path": "${KICAD_SYM_DIR}/Device.kicad_sym"}
},
"pin_map": {
"U1": {"Net-SDA": "5", "Net-SCL": "6"},
"U2": {"Net-SDA": "SDI", "Net-SCL": "SCK"}
}
}

VERSION

This project’s own current version string, read by --version (see DevelopmentStandard.org, "Versioning").

ENVIRONMENT

PACKPAY_COMPONENT_LIBS

The component library directories, separated as the directories of a PATH are: where a relative path of the sources file is looked for. Default: ˜/.local/share/packpay/component_libs. ./configure --with-component-libs=DIR sets the value that make passes on, and ./fetch-component-libs (generated by configure with that value) puts the libraries of the project’s own test designs in the first directory: see fetch-component-libs(1).

KICAD_SYM_DIR

KiCad’s own symbol libraries (default /usr/share/kicad/symbols): the power library that is copied beside the new schematic, and the meaning of ${KICAD_SYM_DIR} in a path of the sources file.

EXIT STATUS

0

The schematic was resolved, laid out, and written successfully.

1

The instructions file or the --sources file could not be opened or parsed; a real component has no --sources entry and no already-known library/part (see UnresolvedComponentError); an anchor’s own real pin identity could not be resolved (see UnresolvedPinError); or no paper-size tier, however generous, made the design fit (a genuine "this circuit needs a human to look at it" signal — see generate_schematic_auto() ’s own docstring); or a circuit above the per-sheet --max-components limit cannot be split: a passive with no non-rail path to any anchor (for example a decoupling capacitor), or an anchor’s own cluster larger than one whole sheet (see above).

2

Invalid command-line usage (e.g. --sources or -o omitted).

EXAMPLES

The complete round trip, instructions back to a working schematic:

ppresolve_components --sources altimeter-sources.json \
-o altimeter.kicad_sch --title "Poor Mans Altimeter" \
--anchor-spacing 100 altimeter-instructions.org
kicad-cli sch erc --exit-code-violations altimeter.kicad_sch

THE BOARD

A design that needs a bigger board than the default gets one computed for it, and the hole names in the instructions ("f3") mean different strips on different boards. When the board is not the default five rows per half, the build instructions therefore state it, once, right after the title:

Board: 7 rows per half (rows a-g above the trench, h-n below).
ppresolve_components
reads it too. If a board configuration is also in effect (--config, $PACKPAY_BREADBOARD_CONFIG or a breadboard.conf beside the instructions) and it disagrees with the line, the run stops with a message naming both numbers: reading the holes with the wrong geometry would put nets on the wrong strips.

PIN IDENTITIES AND THE PIN MAP

When the instructions list an anchor’s pins and name its pins on each rail, those pins are real and the pin map is not consulted for them: the altimeter and the knight-roller come back from their own instructions with no pin map at all. A pin map is still needed for the parts whose pins the instructions do not name: a connector or a cell wired straight to the rails (which pin is the positive one), and a two-pin connector whose pins are not numbered 1 and 2. An instruction text written before the pins were listed still needs the pin map it always needed.

SEE ALSO

ppextract_sources(1), ppparse_instructions(1), ppbuild_instructions(1), ppauto_layout(1), pprender(1), kicad-cli(1), ResearchJournal.org and SessionHandoff.org in packpay’s own documentation set (see Index.org).

AUTHOR

Péter Felecan <pfelecan@gmail.com>, with development assistance from Claude (Anthropic).

COPYRIGHT

Copyright © 2026 Péter Felecan. License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>. This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law.