Workflow: from a real netlist to a placed diagram, and back
v0.72.103.244

Table of Contents

List of Listings

List of Figures

  • Figure 1 The two flows of the pipeline in one drawing.
  • Figure 2 The reverse direction: what is read and what it becomes
  • Figure 3 The altimeter's own schematic, as KiCad draws it, the sheet's border and title block left out
  • Figure 4 The altimeter on a breadboard, as pprender draws it
  • Figure 5 The schematic that comes back out of the instructions, the sheet's border and title block left out

1. Purpose of this document

This document describes the actual, concrete, real, command-line path from a real schematic (or a human-readable set of build instructions) to a breadboard-style diagram, a new KiCad schematic, or both – which real command, in which order, produces which real output. Every command below is one of this project's own command-line tools, run exactly as an end user would run it – never a raw Python function call. One real circuit, this project's own altimeter example (four real components: a Seeeduino XIAO, a BME280 pressure sensor, a micro SD card module, and a LiPo battery), is used throughout, start to finish, rather than a different circuit per stage.

Terms. The words of this document – sector (the code says sector), board, strip, column, row, rail, anchor, passive, jumper, link, tier, and the schematic words (sheet, net, symbol, unit) – are defined once, in the Glossary at the end of the concept document, which also settles where this project's usage differs from the usual breadboard vocabulary (its column is what many makers call a row). Where a term below is unclear, look it up there. The practices the steps below follow, and those the tool does not yet follow, are in the Best practices section. The real boards the instructions are written for are classified in the Taxonomy of breadboards.

It is a practical, verified walkthrough, not a rationale: every command below was actually run and its real output checked, not described from memory. It does not explain why each stage's own algorithm works the way it does, or the history of what was tried and found along the way – that narrative lives in ResearchJournal.org's own dated entries. Read this document to trace a real invocation through the pipeline; read ResearchJournal.org for why each stage is built the way it is.

2. The shared pipeline

Both of this project's own real outputs – a KiCad schematic and a breadboard-style diagram – start from the exact same place: a real netlist, and the exact same classification step. They diverge only at the final placement stage, which is genuinely different for the two output kinds (a breadboard's own rows and columns are not a schematic sheet's own X/Y coordinates), then can converge again: a freshly generated KiCad schematic is, itself, a real .kicad_sch file – exactly the same kind of input this whole pipeline started from. A SEPARATE, real, complementary path (Stage 4) starts from human-readable build instructions instead, and reaches the SAME two real destinations (see Figure 1).

Flow diagram. A KiCad schematic goes through ppnetlist to a netlist and through ppauto_layout to a classification of anchors, passives and rail nets. From there two paths leave: the breadboard path (a breadboard layout, drawn by pprender as an SVG) and the KiCad path (a schematic layout, written by ppkicad_layout as a new .kicad_sch). The new schematic feeds back into the start of the diagram: the loop. A third path starts from build instructions, which ppbuild_instructions makes from the schematic: ppparse_instructions parses them and ppresolve_components resolves the parts, giving a reconstructed breadboard placement to inspect, or a new .kicad_sch that also feeds the loop.

Figure 1: The two flows of the pipeline in one drawing. A schematic becomes a netlist and a classification, then a breadboard drawing or a new schematic, which feeds the start again (the loop); build instructions reach the same destinations.

3. Stage 1: getting a real netlist

Two real, equally valid starting points:

  • An existing schematic, already on disk – either one this project's own tools generated earlier, or a genuinely external design (TestSchemes/ is exactly this: six real KiCad projects, none authored by this project's own tooling – this project's own real altimeter example, TestSchemes/PoorManAltimeter/altimeter.kicad_sch, used throughout this whole document, is one of them). Every real tool below shells out to kicad-cli sch export netlist itself – no separate export step needed. Hierarchical, multi- sheet designs (two of TestSchemes' own six projects) are followed automatically by kicad-cli itself, provided every real sheet file is still present alongside the root (see ResearchJournal.org's own 2026-08-18 TestSchemes entry for a real, confirmed case of what happens when one isn't: a silent, partial netlist, no warning).
  • A circuit with no schematic yet – this project's own two, self-contained example circuits (ppexample_circuit1(1), ppexample_circuit2(1)) build one directly, with no real .kicad_sch file involved at all. Useful for a circuit still being designed, or for this project's own synthetic stress tests.

Verified, the existing-schematic path, using ppnetlist(1) (see README/Index.org's own "Manual pages" section for every real tool this document uses) (see Listing 1):

$ kicad-cli sch export netlist --format kicadsexpr \
    -o altimeter.net TestSchemes/PoorManAltimeter/altimeter.kicad_sch
$ ppnetlist altimeter.net
4 components, 29 nets
  BT1: LiPo 3.7V 250mAh
  U1: Seeeduino XIAO
  U2: BMP280_KK
  U3: Micro SD Card Module
  GND: BT1.2, U1.13, U2.GND, U3.GND
  Net-CS: U1.4, U3.CS
  Net-MISO: U1.10, U3.DATA_OUT
  Net-MOSI: U1.11, U3.DATA_IN
  Net-SCK: U1.9, U3.SCLK
  Net-SCL: U1.6, U2.SCK
  Net-SDA: U1.5, U2.SDI
  VBUS: BT1.1, U1.14, U2.VDD, U3.VDD
  unconnected-(U1-3V3_OUT-Pad12): U1.12
  ...

(the trailing unconnected-(...) entries, one per real, genuinely unused pin on each real symbol, are trimmed above for length – ppnetlist itself prints every one of them).

4. Stage 2: classification, shared by both outputs

ppauto_layout(1) (and every other real tool downstream of it) splits every real component into anchors (3 or more real net memberships – ICs, connectors, anything with enough real pins to anchor other things around) and passives (fewer – resistors, capacitors, diodes, anything that typically attaches to something else rather than anchoring its own), and finds rail nets (power/ ground nets with enough real members, or a real, conventional rail name, that drawing them as ordinary point-to-point wires would be unreadable – drawn as a rail/bus symbol instead on the KiCad side, or a rail stub on the breadboard side) (see Listing 2).

$ ppauto_layout TestSchemes/PoorManAltimeter/altimeter.kicad_sch
Anchors (3): ['U1', 'U2', 'U3']
Passives (0): []
Rail nets excluded from placement: []

Anchor placement:
  U1: starts at column 17
  U2: starts at column 31
  U3: starts at column 5

Passive placement:

Every passive was placed.

BT1, this circuit's own real battery, is deliberately absent from BOTH the anchor and the ordinary-passive lists here – not unplaced, not an error. This board's own real, default configuration already has a rail, and a genuine, simple power source is placed directly ON that rail instead of the ordinary main grid at all – see Stage 4's own dedicated section on this, below, for the real, direct account of why and how.

5. Stage 3a: a breadboard-style diagram

pprender(1) takes classification one step further: a real row/ column placement (which breadboard column each component's own near leg sits in, which row, whether it needed relocating for room – see FutureResearch.org's own row-assignment item history), verified for connectivity and board-bounds before drawing it.

A single, physical breadboard's own real geometry (sector count, per-sector column/row counts, rail presence and grouping) is described by a config file, not assumed – see breadboard_config(5) for the full format, including worked examples; both ppauto_layout(1) and pprender(1) consume it, addressing (sector, column, half), not a single, fixed shape. An assembly of several, genuinely different physical boards is supported the same way, via a separate [multiboard] file naming each individual board's own config in turn.

Verified, continuing directly from Stage 1/2 above, with no explicit config file at all – pprender falls back to a real, small, single-sector default (see resolve_config_path() in breadboard_config(5) for the full lookup order this follows), and this circuit already fits within it (see Listing 3):

$ pprender TestSchemes/PoorManAltimeter/altimeter.kicad_sch \
    -o altimeter-breadboard.svg
connectivity check: 0 failures (every real net's own members checked for a genuine connected path in the layout itself, before any rendering happens)
board-bounds check: 0 failures (every assigned row checked against the real, parametric ROWS_PER_HALF limit -- a real breadboard half only has so many holes per column, whatever that number currently is)
wrote altimeter-breadboard.svg, 723x413.0
hole-collision check: 0 failures (every real lead's own position checked against every other for two different leads landing in the same physical hole)
verify: 0 label overlaps (of 3 labels, checked against all 96 registered regions -- bodies, wires, and labels)
connectivity check after render (far legs included): 0 failures

A genuinely larger circuit that does not fit the default has two real options: an explicit --config file with more room (see breadboard_config(5)), or pprender's own --use-computed flag, which escalates auto_layout.py's own compute_optimal_config() automatically, trying progressively more sectors of the same real shape until the circuit fits or a configurable ceiling is reached.

6. Stage 3b: a new, algorithmically laid-out KiCad schematic

kicad_layout.py's own generate_schematic() (or its self-tuning wrapper, generate_schematic_auto(), which retries with a more generous sheet size automatically rather than requiring a human to guess one) places anchors in a row, attaches passives to whatever they share a net with, positions PWR_FLAGs clear of real label reach, and verifies the whole result against sheet bounds, the title block, and component-to-component overlap – using kicad_gen.py's own small language underneath (see KicadGen.org for that language's own full reference). The result is written out as a real, valid .kicad_sch file: the SAME kind of file Stage 1 started from – closing the loop this document's own title refers to.

ppkicad_layout(1) is the tool for this stage – an existing .kicad_sch, or a netlist already exported from one (.net, the file ppnetlist(1) reads), straight to a NEW, algorithmically laid-out .kicad_sch, no build instructions in between. Split across a root sheet and child sheets (OUTPUT-sheetN.kicad_sch) when above --max-components, the per-sheet limit; every file written is reported. ppresolve_components(1) reaches the same code, but only from build instructions, never from a schematic directly (see Listing 4).

$ ppkicad_layout -o tidy.kicad_sch design.kicad_sch
$ ppkicad_layout --max-components 40 \
      --symbol-lib mylib=libs/mylib.kicad_sym \
      -o split.kicad_sch design.net

A hierarchical source stays hierarchical : a design whose own sheets the netlist carries (rp2040-basic-m1: /, /usb-power/, /flash/) is written as one child sheet per source sheet, named after it, nested as the source is nested – however small, and without being asked. --flatten opts out. A source sheet above --max-components is cut mechanically inside itself. A flat source is unaffected.

A sheet the source uses more than once stays ONE sheet (only reuse that the source itself declares is detected): when the netlist lists one source file for several sheet paths, the output has one shared child file and several sheet symbols, each instance keeping its own references; instances are matched by their tstamps and the match verified, never guessed. Only for a leaf sheet directly under the top level; anything else is written as independent copies.

A part with several units is laid out unit by unit (2026-10-06): a dual op-amp, a quad gate. A symbol's units are all drawn at the same origin, so as ONE component their pins sit on top of each other; ppkicad_layout makes one virtual component per used unit (resolve_components.expand_multi_unit(), key U3@u2), each placed as (unit 2) under the real reference U3. A pin drawn in several units (the LM358N's power pins) is connected in each. The instructions route (ppresolve_components) does the same (0.72.28), expanding at the last moment from the real netlist.

The loop on a design that needs a bigger board, with several ground pins (2026-10-07, the knight-roller loop): the instructions state a non-default board (Board: 7 rows per half ...) and the parser reads it; a pin_map value may be a LIST of real pins; and the breadboard flow wires only POWER nets (rail-like name, or a power pin) to the rails, not every net of more than four members. Open: polarity (FutureResearch.org).

The instructions route can read a design's embedded symbols (2026-10-07): ppextract_sources SCHEMATIC writes the symbols the schematic itself embeds, one library file per nickname, and a sources file whose sym_path entries point at them (else at an installed library), with an empty pin_map for the user – only they know which pin is which. A design that predates a library rename (camera-trap's Conn_01x03_Male) can only be resolved this way.

Symbols are looked for in this order: an explicit --symbol-lib [NICKNAME=]FILE; then, for a .kicad_sch input, the schematic's OWN embedded symbols (and its sub-sheets': what its author drew with, so that no library needs to be installed); then KiCad's standard libraries. A .net carries no symbols, so a project-LOCAL library needs --symbol-lib there; a library that cannot be found stops the run up front, naming it and the components that use it. There is no --sources file here, unlike Stage 4's ppresolve_components(1): that file exists to supply what a build-instruction text can never carry.

The longer way to the SAME destination – generate build instructions from the existing schematic first (ppbuild_instructions(1)), then resolve those into a new one (ppresolve_components(1), Stage 4, below, with a real --sources file) – remains, and is the one that also reaches the breadboard.

7. Stage 4: from build instructions back to a breadboard or a new KiCad schematic

Stages 1-3 above all start from a real .kicad_sch file. This stage covers the OTHER real starting point this project supports: a human-readable set of build instructions (ppbuild_instructions(1)'s own real output) – read back and turned either into a reconstructed breadboard placement, or a genuinely new KiCad schematic, reaching the SAME real destinations Stage 3a/3b do, from the opposite direction (see Figure 2).

Flow diagram. A KiCad schematic becomes build instructions through ppbuild_instructions (stage 3a). The instructions, plain, markdown or org-mode text, are read back by ppparse_instructions into the reconstructed netlist and placement, to verify and inspect, and by ppresolve_components, which needs a real sources file, into a new KiCad schematic, which feeds back into stages 1 and 3b: the loop.

Figure 2: The reverse direction: what is read and what it becomes

Generating instructions, from this circuit's own real schematic, continuing directly from Stage 1-3's own default board (no --config needed) – ppbuild_instructions(1), using its own real, default (plain) format (see Listing 5).

$ ppbuild_instructions TestSchemes/PoorManAltimeter/altimeter.kicad_sch \
    -o instructions.txt
generated 15 steps (position order, plain format)
wrote instructions.txt

Listing 6 shows the steps of that file that matter here; the others are left out:

1. Place the Micro SD Card Module (TRANSFLASH, ref U3) straddling the
   trench, with its leftmost pin in hole a5. Its pins, from the left:
   CS in a5, DATA_IN in a6, DATA_OUT in a7, GND in f8, SCLK in f9, VDD
   in f10. It also connects to the power (+) and ground (-) rails. Its
   pins on the power (+) rail: VDD. Its pins on the ground (-) rail:
   GND. No footprint is on file for it: check that it can be plugged
   into a breadboard.
   [https://www.google.com/search?q=TRANSFLASH%20datasheet]
...
4. Connect a black jumper wire from hole h8 to hole h23 (net GND).
...
14. Connect the LiPo 3.7V 250mAh (BT1) directly to the north rail. Its
    pins on the power (+) rail: 1. Its pins on the ground (-) rail: 2.
    [https://www.google.com/search?q=Battery_Cell%20datasheet]
15. Connect a red jumper wire from the north rail to the south rail.

Steps 14-15, BT1's own real placement, are explained below.

Every wire step names the colour of its wire (a black jumper wire for ground, a red one for power, one colour for each signal), and the drawing of the breadboard uses the same colours. The rule is the practice Colour-code the wires.

The same instructions for a child: with --child-level the same steps are written in words that a child of about seven can follow, instead of the default adult phrasing (ppbuild_instructions(1)). The steps are the same fifteen, in the same order, with the same holes and the same wire colours; what changes is the sentences. Listing 7 writes them, and Listing 8 shows the same four steps as Listing 6, so that the two sets can be read side by side:

$ ppbuild_instructions --child-level \
    TestSchemes/PoorManAltimeter/altimeter.kicad_sch \
    -o instructions-child.txt
generated 15 steps (position order, plain format)
wrote instructions-child.txt
1. Find the Micro SD Card Module part. It's called U3. Lay it across
   the little gap in the middle of the board, with its first pin going
   into hole a5. Its pins, from the left: CS in a5, DATA_IN in a6,
   DATA_OUT in a7, GND in f8, SCLK in f9, VDD in f10. It also connects
   to the power (+) and ground (-) rails. Its pins on the power (+)
   rail: VDD. Its pins on the ground (-) rail: GND. No footprint is on
   file for it: check that it can be plugged into a breadboard.
   [https://www.google.com/search?q=TRANSFLASH%20datasheet]
...
4. Use a black wire to connect hole h8 to hole h23.
...
14. Find the LiPo 3.7V 250mAh part. It's called BT1. Connect it
    straight to the north rail, not to the middle of the board. Its
    pins on the power (+) rail: 1. Its pins on the ground (-) rail: 2.
    [https://www.google.com/search?q=Battery_Cell%20datasheet]
...
15. Use a red wire to connect the north rail to the south rail.

What differs, in these four steps:

  • A part is placed in the adult set, with its symbol and reference in brackets and its position "straddling the trench, with its leftmost pin in hole a5"; in the child set it is found by the name written on it ("It's called U3") and laid "across the little gap in the middle of the board, with its first pin going into hole a5".
  • A wire is "a black jumper wire" with the name of its net in brackets in the adult set, and "a black wire" in the child set, which names no net.
  • The battery step, which says "directly to the north rail" in the adult set, spells it out in the child set: "straight to the north rail, not to the middle of the board".

Everything that a person needs in order to put each part in the right hole and each wire between the right holes is the same. The tools that read instructions back (ppparse_instructions(1) and ppresolve_components(1)) read the adult phrasing only: the words for a child are for a person to follow, and are not read back.

Reading instructions back, to inspect the reconstructed breadboard placement alone (no KiCad involved at all) – ppparse_instructions(1)'s own --debug flag additionally annotates each real component's own column/rail membership (see Listing 9):

$ ppparse_instructions instructions.txt --debug
4 components, 8 nets
  BT1: LiPo 3.7V 250mAh
  U1: Seeeduino XIAO (anchor, col 17), rail net(s) GND, VBUS
  U2: BMP280_KK (anchor, col 31), rail net(s) GND, VBUS
  U3: Micro SD Card Module (anchor, col 5), rail net(s) GND, VBUS
...

BT1 carries no real (anchor, col ...)~/~(passive, col ...) annotation at all here – it is on neither the main anchor grid nor the ordinary passive one; its own real GND/VBUS net membership is confirmed further down this same real output instead (both real nets list it directly).

A genuine, simple power source (a passive power_sources.py's own classify_power_sources() also identifies as a source – a battery being the direct, real example) is placed differently on the breadboard side whenever the board's own configuration has a real rail at all – a breadboarder does not leave a battery "in plain on the board… very close to a major and complex component" the way an ordinary passive still can, even under this project's own existing rail-attachment fallback (which still reserves a real, if unshared, column on the main grid). Instead: straight to a vertical (left/right) rail when the board has one, distributing from there by jumper to BOTH horizontal rails; otherwise straight to the north horizontal rail, with a single jumper to the south one – exactly steps 14-15, above. This placement, and ppparse_instructions(1)'s own reading of it back into a reconstructed breadboard netlist, are both fully supported.

Resolving instructions into a new KiCad schematic IS supported for a rail-direct power source too – ppresolve_components(1) no longer refuses it. Continuing this same, real walkthrough with the altimeter's own instructions regenerated against a board WITH a left rail (altimeter-with-rail.conf, [rail.left] present = true – see breadboard_config(5) for the full format) (see Listing 10):

$ ppbuild_instructions TestSchemes/PoorManAltimeter/altimeter.kicad_sch \
    -o instructions-with-rail.txt \
    --config TestSchemes/PoorManAltimeter/altimeter-with-rail.conf
generated 16 steps (position order, plain format)
wrote instructions-with-rail.txt

Resolving this into a new KiCad schematic needs one more real, direct input the breadboard-only path does not: a --sources JSON file, mapping each real ref to where its own real KiCad/Eagle symbol actually lives, plus a pin_map entry for every real net name the instructions state that isn't already a bare pin number (an anchor's own SDA/SCL/CS-style net names, and – CORRECTED 2026-09-16, closing a real, direct gap this same session's own rail-membership work opened – "GND""VBUS" too, for any anchor that now states its own rail membership in prose at all; see resolve_components(5)'s own full field reference). A rail-direct power source itself needs NO pin_map entry at all for the common case – resolve_pin_identities() (resolve_components.py) reads its own resolved symbol's own real pin names directly (BT1's own real Device:Battery_Cell, pins "1""+", "2""-") and resolves its own real polarity automatically; a pin_map entry is only ever needed as a real, explicit fallback, for a symbol whose own real pin names don't confidently match this project's own +- vocabulary at all (resolve_components(5)'s own full field reference covers this too) (see Listing 11):

$ ppresolve_components instructions-with-rail.txt \
    --sources TestSchemes/PoorManAltimeter/altimeter-sources.json \
    -o new-altimeter.kicad_sch \
    --anchor-spacing 100 --title "Altimeter workflow demo"
wrote new-altimeter.kicad_sch
wrote new-altimeter.kicad_pro
$ kicad-cli sch erc --severity-error --exit-code-violations \
    new-altimeter.kicad_sch
Found 0 violations
Saved ERC Report to new-altimeter-erc.rpt

Verified, actually closing this loop too: running ppbuild_instructions again on that freshly-generated schematic reproduces the identical instruction text (only the title line differs, since it names the source file) – confirming, not merely asserting, that the new schematic really is electrically equivalent to what the instructions described, not merely "some valid-looking schematic".

Not a language reference (KicadGen.org is that, for kicad_gen.py's own placement primitives specifically) and not a rationale for WHY each algorithm works the way it does (ResearchJournal.org's own dated entries are that, including every real bug found building each stage above). This document exists to answer one question directly: given a real schematic, a real netlist, or a real set of build instructions, which real, actual command, in which order, gets you from one real representation to another – confirmed by running it, not assumed.

8. The whole loop, once, on the altimeter

This section runs the loop once, from the altimeter's own schematic round to a new one, and shows every artefact. Four parts make the circuit: a battery, a processor board, a pressure sensor and a memory card module, joined by six signals and the two supply nets (see Figure 3).

Four parts on a KiCad sheet, far apart: a battery with VBUS and GND labels, a Seeeduino XIAO board, a BMP280 sensor and a micro SD card module, joined by labels named Net-CS, Net-SDA, Net-SCL, Net-MISO, Net-MOSI and Net-SCK.

Figure 3: The altimeter's own schematic, as KiCad draws it, the sheet's border and title block left out

The build instructions are made first, as in Stage 4 (Listing 5 and Listing 6). The breadboard drawing is made from the same schematic by pprender(1) (Listing 12); it shows the three anchors that the instructions place, the wires in the colours the instructions name, and the legend (Figure 4). The battery is not on the board: the instructions name it as the source of the supply rails.

$ pprender TestSchemes/PoorManAltimeter/altimeter.kicad_sch -o altimeter.svg
connectivity check: 0 failures (every real net's own members checked for a genuine connected path in the layout itself, before any rendering happens)
board-bounds check: 0 failures (every assigned row checked against the real, parametric ROWS_PER_HALF limit -- a real breadboard half only has so many holes per column, whatever that number currently is)
wrote altimeter.svg, 723x413.0
hole-collision check: 0 failures (every real lead's own position checked against every other for two different leads landing in the same physical hole)
verify: 0 label overlaps (of 3 labels, checked against all 96 registered regions -- bodies, wires, and labels)
connectivity check after render (far legs included): 0 failures

A breadboard with three anchors straddling the trench, U3 the SD card module, U1 the XIAO board and U2 the BMP280 sensor, joined by coloured wires: red for power, black for ground and one colour for each signal; the legend names the three parts.

Figure 4: The altimeter on a breadboard, as pprender draws it

The way back starts from the instructions alone. ppparse_instructions(1) reads them and prints what they say: the parts, and the nets with their pins (Listing 13).

$ ppparse_instructions instructions.txt
4 components, 8 nets
  BT1: LiPo 3.7V 250mAh
  U1: Seeeduino XIAO
  U2: BMP280_KK
  U3: Micro SD Card Module
  VBUS: U3.VDD, U1.14, U2.VDD, BT1.1
  GND: U3.GND, U1.13, U2.GND, BT1.2
  Net-CS: U3.CS, U1.4
  Net-MISO: U3.DATA_OUT, U1.10
  Net-MOSI: U3.DATA_IN, U1.11
  Net-SCK: U3.SCLK, U1.9
  Net-SCL: U1.6, U2.SCK
  Net-SDA: U1.5, U2.SDI

ppresolve_components(1) turns the same instructions into a new schematic, with the help of a sources file that says where each part's symbol is (Listing 14); the altimeter's own sources file is TestSchemes/PoorManAltimeter/altimeter-sources.json, which names the libraries of its parts relative to the component library directory (PACKPAY_COMPONENT_LIBS, by default $HOME/.local/share/packpay/component_libs; fetch-component-libs(1) puts them there, as the README says).

$ ppresolve_components --sources TestSchemes/PoorManAltimeter/altimeter-sources.json \
    instructions.txt -o altimeter-loop.kicad_sch
wrote altimeter-loop.kicad_sch
wrote altimeter-loop.kicad_pro

Figure 5 shows the result.

The same four parts and the same labels, arranged anew and closer together: the XIAO board on the left with the battery below it, the sensor and the SD card module to its right.

Figure 5: The schematic that comes back out of the instructions, the sheet's border and title block left out

What the loop keeps, and what it does not. The circuit is kept: every net of the new schematic has the same pins as the net of the original (checked by exporting both netlists with kicad-cli and reading them with ppnetlist(1)). The drawing is not: the instructions carry no coordinates, so the arrangement is decided anew, on a smaller sheet and with the parts closer together. It is decided the same way every time: the same instructions and the same sources file give the same arrangement and, with the same date, the same file, byte for byte (the identifiers of the items 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). The title block of the new sheet takes its title from the name of the instructions' file and its date from the day of the run unless --date gives one, which is why the pictures leave the sheet's border out. Pins are kept only as far as the sources file says: the altimeter's sources file closes the loop with the same pins on every net; the one that ppextract_sources(1) writes for camera-trap has an empty pin map, and a resistor and a switch came back with their two pins exchanged.

Author: Péter Felecan
assisted by Claude Sonnet 5.5 (Anthropic)

Created: 2026-10-18 Sun 14:36