ppkicad_layout - lay out a KiCad schematic anew, from an existing schematic or its netlist
ppkicad_layout
[--symbol-lib [NICKNAME=]FILE]...
[--kicad-cli CMD] [--kicad-version
N] [--max-components N]
[--flatten] [--no-proximity] [--paper
SIZE] [--cache-dir DIR]
[--title TITLE] [--date DATE]
[--rev REV] [--company NAME]
[--anchor-spacing MM] [--row-gap
MM] [--row-spacing MM]
[--debug] [--quiet] -o FILE
INPUT
ppkicad_layout {--help|--version}
ppkicad_layout reads a real KiCad schematic, or a netlist exported from one, and writes a new KiCad schematic of the same circuit, placed and wired algorithmically by this project’s own layout engine (kicad_layout.py). It is Stage 3b of the pipeline described in Workflow.org: an existing design in, a re-laid-out design out, with no build instructions in between (ppbuild_instructions(1) followed by ppresolve_components(1) reaches the same destination the long way round).
INPUT is either a .kicad_sch schematic, whose netlist is exported with kicad-cli(1) (see --kicad-cli), or a .net file already exported with kicad-cli sch export netlist --format kicadsexpr (the file ppnetlist(1) reads); the kind is taken from the file name’s extension. A hierarchical input is flattened by the export, as for every other tool in this project.
A circuit above the per-sheet component limit (--max-components, default 150) is written as a root sheet plus child sheets: the root is FILE, the children are FILE-sheetN.kicad_sch beside it, joined by real hierarchical sheet pins and labels, every net keeping its exact name. A child that itself does not fit on any paper size is split further, to as many levels as the circuit needs (FILE-sheet1-sheet2.kicad_sch, and so on). A circuit that is under the limit but fits no single sheet at any paper size is split the same way, and a note says so on standard error.
A source design that is itself hierarchical stays hierarchical, however small it is: one child sheet per source sheet, named after it (usb-power, flash), nested as the source is nested, the parts on the source’s own root sheet on a child called Root. The child files are named FILE-name.kicad_sch (name made file-safe and unique). A source sheet above --max-components is cut mechanically inside itself, nested under its own name. The sheets come from the netlist’s own sheet structure, so a design built by hand or reconstructed from build instructions, and a flat source, are unaffected. Use --flatten to ignore the source’s sheets.
A sheet file the source uses MORE THAN ONCE (one .kicad_sch instantiated by several sheet symbols, as in KiCad’s own complex_hierarchy demo) is written the same way: one child file, named after its source file (FILE-ampli_ht.kicad_sch), several sheet symbols with their own names, and every symbol in the shared file carrying one instance entry per instance with that instance’s own reference designators. The parts of the instances are matched by their identifiers in the source, and the match is verified (same parts, same values, same connections, the same pins reaching outside); an instance that fails any of it is written as an independent sheet. Reuse is followed only for a sheet that holds no sub-sheets of its own, directly under the top-level sheet, and only when the source declares it: identical sub-circuits in a flat design are not detected.
A part whose symbol has several units (a dual op-amp, a quad gate) is laid out one unit at a time: each unit the design uses is placed as its own symbol, with its own pins, under the part’s real reference (U3A, U3B, ...). A pin drawn in several units, such as the power pins of many op-amps, is connected in each of them.
Every component must resolve to a real KiCad symbol, looked for in this order: a library given with --symbol-lib; then, for a .kicad_sch INPUT, the symbols the schematic itself embeds, and those of the sheet files it includes, recursively (exactly what its author drew with: no library needs to be installed anywhere, and a symbol since renamed in KiCad’s own libraries still resolves); then KiCad’s standard libraries ($KICAD_SYM_DIR). A .net INPUT carries no symbols, so a component from a project-local library needs --symbol-lib. Unlike ppresolve_components(1) there is no --sources file: a netlist already carries every pin’s real number, which a build-instruction text never does.
The output is reproducible: the same input, 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, its parts and its nets, and from nothing else (not from the title, not from any date, not from the way the input is written, not from its positions), 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.
|
INPUT |
A .kicad_sch or .net file, as described above. |
-o FILE, --output FILE
(Required.) Output path of the root .kicad_sch file. The project file (FILE’s base name with .kicad_pro) and, for a split design, the child sheets are written beside it. The output may not be the input.
--symbol-lib [NICKNAME=]FILE
A symbol library (.kicad_sym) for the netlist’s components, taking precedence over everything else. A .kicad_sch INPUT needs none: its own embedded symbols are used. A .net INPUT carries no symbols, so a project-local library must be given here. NICKNAME is the library name the netlist uses; it defaults to FILE’s base name without .kicad_sym, the usual KiCad convention for a project-local library. May be repeated. A library that cannot be found at all is reported up front, once per library, naming the components that use it.
--kicad-cli CMD
The kicad-cli command, used for a .kicad_sch INPUT, and, unless --kicad-version is 7, to convert the files written. May be several words (an AppImage: "./KiCad.AppImage kicad-cli"). Default: $KICAD_CLI 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. This tool writes the KiCad 7 format itself (version 7: no kicad-cli needed); any other version is the format of the installed kicad-cli, to which every file of the output is converted, one kicad-cli sch upgrade call per file, so the output opens in that KiCad without the banner about an older file format. A version that cannot be written (neither 7 nor the installed kicad-cli’s) is refused before anything is done, with what can be done instead; there is no downgrade, and no writer for the versions between. The circuit is the same in both formats.
--max-components N
The per-sheet component limit (a positive integer). Above it the design is split across sheets rather than refused. Default: 150, this project’s own breadboard_config.DEFAULT_MAX_COMPONENTS (see that constant’s own docstring for why).
--flatten
Ignore the source design’s own sheets and lay the circuit out as one sheet, split mechanically only above --max-components. Default: a hierarchical source stays hierarchical.
--no-proximity
Split a single-sheet .kicad_sch source purely mechanically. By default, when such a source has to be split across sheets, the parts that have no non-rail path to any anchor (decoupling capacitors, islands of passives) go with the anchor they are DRAWN nearest to in the source (the distance between symbol origins), and a connected group of them is never cut through; the mechanical rule attached them afterwards to the sheet that shared most nets with them, which for a part whose nets are all rails is close to arbitrary (on KiCad’s own single-sheet demos 0 to 71% of them ended on the sheet of the anchor drawn nearest). A source that has sheets keeps its own grouping, and a netlist input has no drawing, so neither is affected. The environment variable PACKPAY_ORPHAN_PROXIMITY=0 does the same. A trade-off, measured: signal crossings between sheets are equal or lower on most demos and slightly higher on two (electric: 4 against 0 at a limit of 25; test_xil_95108: 5 against 3 at 21).
--paper SIZE
The paper size to try first: A4, A3, A2, A1 or A0. Each larger size is still tried, in order, if the design does not fit. Default: start at A4.
--cache-dir DIR
Where the resolved symbol libraries are collected. Default: a fresh temporary directory, discarded on exit.
--title TITLE, --date DATE, --rev REV, --company NAME
Title-block fields. Defaults: the input’s base name, today’s date (YYYY-MM-DD), empty, empty.
--anchor-spacing MM
The MINIMUM horizontal spacing between the main components, in mm (default 48, must be greater than 0); widened automatically for a large component so neighbours never overlap. Increase it if generation fails with "placed close enough to genuinely overlap" at every paper size.
--row-gap MM
The MINIMUM vertical gap between the main row and the dedicated rows for parts tied only to power rails, and between wrapped rows (default 15, at least 0). It is raised automatically where the parts’ own height plus the two 5.08 mm label stubs at their ends need more. Increase it if generation fails with a label-collision error naming a dedicated row.
--row-spacing MM
Horizontal spacing within a dedicated row (default 20, greater than 0).
--debug
Also print, to standard error, every paper-size attempt and split attempt the layout engine makes, and show a Python traceback for an unexpected internal error.
--quiet
Suppress the "wrote FILE" lines and the split note.
--help
Print a usage summary and exit.
--version
Print the version and exit.
|
0 |
The schematic was laid out and written. | ||
|
1 |
The input could not be read, was not a .kicad_sch or .net, or had no components; kicad-cli could not be run or failed; a --symbol-lib file does not exist; a symbol library or symbol could not be found; the output would overwrite the input or its directory does not exist; or no paper size and no split made the design fit (a genuine "this circuit needs a human to look at it" signal, see generate_schematic_auto() ’s own docstring), including a part tied only to rails that has no non-rail connection to place it by. | ||
|
2 |
Invalid command-line usage (a missing -o, a bad --paper or a non-positive number). |
KICAD_CLI
Default for --kicad-cli.
KICAD_SYM_DIR
KiCad’s standard symbol libraries (default: /usr/share/kicad/symbols).
PACKPAY_KICAD_VERSION
Default for --kicad-version (otherwise 10).
PACKPAY_ANCHOR_REFINE
On unless set to 0: the order in which the anchors are laid out in a row is a connectivity order refined by a local search on the distance between anchors that share a net (see ppbuild_instructions(1)); a design that places related parts closer reads better. Set to 0 for the earlier order.
PACKPAY_ORPHAN_PROXIMITY
On unless set to 0: see --no-proximity.
FILE, FILE-sheetN.kicad_sch
The root, and for a mechanically split design the child sheets (nested: FILE-sheetN-sheetM.kicad_sch).
FILE-name.kicad_sch
For a design that follows its source’s own sheets, one child per source sheet (name as in the source; nested sheets: FILE-name-subname.kicad_sch).
FILE.kicad_pro
The KiCad project file, belonging to the root.
Re-lay-out an existing schematic:
ppkicad_layout
-o tidy.kicad_sch design.kicad_sch
kicad-cli sch erc --exit-code-violations tidy.kicad_sch
Force a split at 40 components per sheet, from an exported netlist, with a project-local library:
kicad-cli sch
export netlist --format kicadsexpr \
-o design.net design.kicad_sch
ppkicad_layout --max-components 40 \
--symbol-lib mylib=libs/mylib.kicad_sym \
-o split.kicad_sch design.net
ppnetlist(1), ppauto_layout(1), ppresolve_components(1), ppbuild_instructions(1), kicad-cli(1), Workflow.org (Stage 3b) and ResearchJournal.org in packpay’s own documentation set (see Index.org).
Péter Felecan <pfelecan@gmail.com>, with development assistance from Claude (Anthropic).
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.