Metadata-Version: 2.4
Name: bblayout
Version: 0.0.1
Summary: Lay electronic schematics out on a solderless breadboard (instead of a PCB) - built for AI agents and humans.
License: Apache-2.0
Keywords: breadboard,electronics,schematic,netlist,layout,llm,ai
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# bblayout

**Lay electronic schematics out on a solderless breadboard instead of a PCB.**

`bblayout` takes a netlist (components plus the nets their pins connect to) and
produces a real breadboard build: where every part goes, which jumper wires to
add, and step-by-step instructions. It also checks any layout for shorts,
open nets and physical conflicts. It was built so AI agents can go from "design a
circuit" to "here's exactly how to build it", but it works just as well from
Python or the command line.

- Pure Python, no dependencies, Python 3.9+
- Board templates: mini (170), half (400), full (830), full with split rails, double (1660), plus `"auto"`
- Footprints: DIP ICs (0.3" and 0.6"), inline parts (TO-92, headers, trimmers), flexible two-lead parts
- Auto-placement and routing that use the power rails and avoid jumper wires where possible
- An independent verifier for layouts edited by hand or by an AI
- Output as ASCII maps, SVG drawings, build steps and JSON
- PCB-like export for simulation: SPICE with breadboard parasitics, KiCad `.kicad_pcb`, and per-net trace reports
- Ready-made LLM tool definitions (Anthropic and OpenAI formats)

## Install

```bash
pip install -e .
```

## Quick start

```python
from bblayout import Schematic, ic, resistor, capacitor, led, auto_layout

sch = Schematic("555 blinker", [
    ic("U1", "NE555", ["GND", "TRIG", "OUT", "VCC", "CTRL", "TRIG", "DIS", "VCC"],
       labels=["GND", "TRIG", "OUT", "RESET", "CTRL", "THRES", "DIS", "VCC"]),
    resistor("R1", "VCC", "DIS", "10k"),
    resistor("R2", "DIS", "TRIG", "68k"),
    capacitor("C1", "TRIG", "GND", "10u", polarized=True),
    capacitor("C2", "CTRL", "GND", "10n"),
    resistor("R3", "OUT", "LED_A", "470"),
    led("D1", "LED_A", "GND", "red"),
])

layout = auto_layout(sch, "half")      # or "mini", "full", "full_split", "double", "auto"
print(layout.ascii())
print(layout.instructions())
assert layout.verify().ok
open("blinker.svg", "w").write(layout.svg())
```

```
     1           5           9
  T+    .  *  B  .  .     *  .   VCC
  T-    .  .  .  .  E     *  .   GND

   a .  .  *  C  .  E  .  .  .
   b .  .  .  B  .  .  .  .  .
   c .  .  .  .  *  .  .  .  .
   d .  .  .  .  C  .  .  .  .
   e .  .  A  A  A  A  .  .  .
     ~~~~~~~~~~~~~~~~~~~~~~~~~
   f .  .  A  A  A  A  .  .  .
   g .  .  D  D  .  .  .  .  .
   h F  =  =  =  F  .  .  .  .
   i G  =  G  *  .  .  .  .  .
   j .  .  *  .  .  *  .  .  .

  B-    .  *  .  .  .     *  .   GND
  B+    .  .  .  .  *     *  .   VCC

  A = U1 NE555 (DIP-8)
  B = R1 10k (two-lead)
  ...
```

```
1. Insert U1 NE555 (DIP-8) straddling the centre channel, pin 1 at f3, notch facing left.
     pin 1 (GND)      -> f3    [GND]
     ...
5. Insert R1 10k (resistor):
     pin 1            -> T+4   [VCC]
     pin 2            -> b4    [DIS]
...
10. Wire (red, ~0.3"): a3 -> T+3   [VCC]
```

## Breadboard templates

| Template     | Points | Columns | Power rails                       |
|--------------|-------:|--------:|-----------------------------------|
| `mini`       |    170 |      17 | none                              |
| `half`       |    400 |      30 | 4 × 25 holes                      |
| `full`       |    830 |      63 | 4 × 50 holes                      |
| `full_split` |    830 |      63 | 4 × 50 holes, split in the middle |
| `double`     |   1660 |     126 | 4 × 100 holes                     |
| `auto`       |        |         | smallest of mini → half → full → double that fits |

Aliases such as `"bb830"`, `"400"` and `"half-size"` work too. To define a custom
board, use `Breadboard(columns=..., rails=..., rail_groups=..., split_rails=...)`
or `Breadboard.from_dict({...})`.

### Hole names

- Terminal holes: row `a`–`j` plus column, e.g. `e12`. Rows `a`–`e` and `f`–`j`
  sit on either side of the centre channel, and the five holes of one column on
  one side are connected (a *strip*).
- Power-rail holes: `T+`, `T-` (top) and `B+`, `B-` (bottom) plus the column they
  line up with, e.g. `T+5`. Rail holes come in groups of five, like on real boards.

## Schematic JSON (the format for AI agents)

```json
{
  "name": "NPN switch",
  "components": [
    {"ref": "R1", "kind": "resistor", "value": "1k",  "pins": ["IN", "BASE"]},
    {"ref": "Q1", "kind": "npn", "part": "2N3904", "pinout": "EBC",
     "pins": {"E": "GND", "B": "BASE", "C": "LED_K"}},
    {"ref": "D1", "kind": "led", "value": "red", "pins": {"A": "LED_A", "K": "LED_K"}},
    {"ref": "R2", "kind": "resistor", "value": "330", "pins": ["VCC", "LED_A"]},
    {"ref": "U1", "kind": "ic", "part": "LM358",
     "pins": ["OUT", "IN-", "IN+", "GND", null, null, null, "VCC"]}
  ],
  "rail_nets": {"T+": "VCC", "T-": "GND"}
}
```

- `pins` is either a list in pin order or a `{pin: net}` object. Use `null` for an unconnected pin.
- `kind` picks a default footprint:
  - two-lead: `resistor`, `capacitor`, `electrolytic` (+/-), `led` (A/K), `diode` (A/K), `inductor`, `crystal`, `button`, ...
  - inline: `transistor`/`npn`/`pnp` (E,B,C; override with `pinout`), `mosfet` (G,D,S), `regulator` (IN,GND,OUT), `potentiometer` (1,W,3), `header`
  - DIP: `ic` (pins numbered from 1)
- Give an explicit `footprint` (`{"type": "dip", "pins": 28, "width": 6}`) for anything unusual.
- `rail_nets` is optional. If you leave it out, nets named `VCC`/`VDD`/`5V`/`3V3`/... go on the `+` rails and `GND`/`VSS` on the `-` rails.

Built-in examples: `bblayout.circuits.get(name)` for `led_resistor`,
`button_led`, `555_blinker`, `transistor_switch`, `opamp_amplifier`,
`regulated_supply`, `led_chaser`.

## Using it from an LLM agent

`bblayout.tools` exposes four tools: `breadboard_layout`, `breadboard_verify`,
`breadboard_export` and `breadboard_templates`. They include a JSON schema for the schematic format.

```python
from bblayout.tools import tool_definitions, handle_tool_call

tools = tool_definitions()            # Anthropic format; tool_definitions("openai") for OpenAI
...
result = handle_tool_call(block.name, block.input)   # dict: ok, verification, instructions, ascii, layout
```

`examples/claude_agent.py` is a complete tool-use loop with the Anthropic SDK.
An agent can also change the returned `layout` JSON (move a part, add a wire)
and pass it to `breadboard_verify`. The verifier rebuilds connectivity from the
board itself and reports `short`, `open`, `hole_conflict`, `covered_hole`,
`footprint`, `off_board`, `unplaced` and `rail_mismatch` issues.

## Command line

```bash
bblayout templates                                   # list boards and example circuits
bblayout example 555_blinker -o blinker.json         # dump an example schematic
bblayout layout blinker.json -b half -o layout.json --svg layout.svg
bblayout layout 555_blinker -b auto                  # built-in circuits work directly
bblayout verify layout.json
bblayout export layout.json --spice out.cir --kicad out.kicad_pcb --report
```

(`python -m bblayout ...` works without installing.)

## PCB-like export for simulation

A breadboard is a crude PCB. Each terminal strip and power rail is a phosphor-bronze
spring clip (a wide, thin track), each jumper is a round copper wire, and every
inserted lead adds contact resistance. `bblayout` turns a layout into that copper
model with real track widths, so the result can go into a simulator or a PCB tool.

```python
layout = auto_layout(circuits.get("555_blinker"), "half")

model = layout.pcb()                    # copper model: traces with width, length, R, L
print(model.report())                   # trace count, length and resistance per net

open("blinker.cir", "w").write(layout.spice(analysis=".tran 1m 2"))     # with parasitics
open("ideal.cir", "w").write(layout.spice(parasitics=False))            # ideal, for comparison
open("blinker.kicad_pcb", "w").write(layout.kicad_pcb())                # KiCad board
```

```bash
bblayout export 555_blinker -b half --spice blinker.cir --kicad blinker.kicad_pcb \
    --pcb-json copper.json --supply VCC=9 --analysis ".tran 1m 2" --report
```

```
  net          traces clips wires contacts  length mm   R sum mOhm L sum nH  eq. 1oz width mm
  GND               8     6     2        8      89.28       171.95    67.03              9.31
  TRIG              5     4     1        6      37.18       124.37    25.86              9.31
  VCC               9     6     3        9     111.06       193.57    85.06              9.31
  ...
```

What is modelled (all values can be changed with `BreadboardPhysics`):

| Element | Default geometry | Becomes |
|---|---|---|
| Terminal-strip / rail clip | 1.5 × 0.3 mm phosphor bronze (ρ = 1.1e-7 Ω·m) | B.Cu track; SPICE R + L per segment between used holes |
| Jumper wire | 22 AWG solid Cu (Ø 0.644 mm), span + 2 mm per end | F.Cu track; SPICE R + L |
| Lead / wire insertion | 20 mΩ contact | SPICE series R |
| Neighbouring strips | 2 pF per adjacent pair; rail pairs 0.5 pF per hole | SPICE C between nets |
| Bench supply | plugs into the end of its rail | SPICE V source + contact |

- **SPICE** (ngspice / LTspice / Xyce syntax): resistors, capacitors, inductors,
  LEDs, diodes, BJTs, MOSFETs, pots (`.param POS_RV1`) and switches
  (`.param R_SW1`) become native elements with simple generic models. ICs become
  `X` subcircuit calls, and the deck lists the `.include` lines for you to point at
  vendor models. Supply voltages are guessed from net names (`5V`, `3V3`, and
  `VCC` = 5 V) unless you pass `supplies={...}`. Unconnected pins get a 1 TΩ
  resistor to ground so the DC operating point exists.
- **KiCad** (`.kicad_pcb`, KiCad 6+): parts are through-hole footprints at their
  holes, clips are B.Cu tracks, jumpers are F.Cu tracks, wire ends are vias, and
  the board outline is the breadboard. `track_width="equivalent"` swaps the
  physical widths for the 1 oz copper width with the same resistance. Jumpers are
  insulated wires above the board, so where one crosses a pad KiCad DRC will
  report a clearance violation; that is expected.
- **JSON** (`model.to_dict()`): every trace (layer, width, thickness, length, R,
  L, equivalent 1 oz width, IPC-2221 ampacity), every contact and every coupling
  capacitance, plus per-net totals.

AI agents get the same through the `breadboard_export` tool.

## How the auto-layout works

1. Supply nets are assigned to the power rails.
2. DIP ICs are placed left to right across the centre channel, with free columns between them.
3. The other parts are placed greedily, most-connected first. Each legal position
   gets a score: a lead in a strip that already carries its net costs nothing, a new
   strip costs a little, and a strip that needs a jumper back to its net costs more
   (more for longer jumpers). A lead can go straight into a rail. Parts may not arch
   over a chip, and a part lying flat along a row covers the holes under its body.
4. Every used strip keeps a free hole for wiring. Strips on supply nets are wired
   to the nearest rail of that net, and rails are tied together only where needed.
   Other nets are joined with a short spanning tree of jumpers that fits the free
   holes in each strip.

Layouts are deterministic. `auto_layout(..., fixed={"U1": "f20"})` pins chosen
parts in place, and `LayoutOptions` adjusts IC spacing, reserved holes and rail
behaviour.

**Limitations:** jumpers are straight point-to-point wires, so they can pass over
chips. The placer is greedy, so the layout is good but not optimal. Each DIP has
one orientation (pin 1 bottom-left).

## Development

```bash
python -m unittest discover -s tests     # or: pytest
```

## License

Apache-2.0
