Metadata-Version: 2.4
Name: electronics_design
Version: 0.2.1
Summary: General-purpose Python utilities for validating LTspice netlists.
Author: brosnan yuen
License-Expression: MIT
Project-URL: Homepage, https://pypi.org/project/electronics_design/
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: kicad-tools[all]>=0.20.0
Requires-Dist: numba>=0.67.0
Requires-Dist: networkx>=3.6.1
Requires-Dist: numpy>=2.5.3
Requires-Dist: Pillow>=12.3.0
Dynamic: license-file

# electronics_design

Python library for validating, converting, plotting, and comparing LTspice schematic (`.asc`), symbol (`.asy`), and netlist (`.net`) files, plus KiCad schematic (`.kicad_sch`) validation.  Also supports symbol-pose resolution, automatic symbol placement, and orthogonal wire routing from netlists.

## API Reference

### ASC Validation

| Function | Returns |
|---|---|
| `is_valid_ltspice_asc_header(filepath)` | `(bool, str)` |
| `is_valid_ltspice_asc_spacing(filepath)` | `(bool, str)` |
| `is_valid_ltspice_asc_footer(filepath)` | `(bool, str)` |
| `is_valid_ltspice_asc_file(filepath)` | `(bool, str)` |

- **Header** requires first nonblank line to be `Version` / `VERSION` and second to be `SHEET`.
- **Spacing** validates keyword support and token structure of every ASC record.
- **Footer** ensures at least one simulation analysis directive (`.tran`, `.ac`, `.dc`, `.op`, `.tf`, `.noise`, `.fra`) is present in a `TEXT !...` record.
- **Whole-file** composes header, spacing, and footer validators.

Error messages: `"File not found!"`, `"No permission to read file!"`, or `"<type> information is invalid! Line <n>"`.

### KiCad Schematic Validation

| Function | Returns |
|---|---|
| `is_valid_kicad_sch_header(filepath)` | `(bool, str)` |
| `is_valid_kicad_sch_spacing(filepath)` | `(bool, str)` |
| `is_valid_kicad_sch_footer(filepath)` | `(bool, str)` |
| `is_valid_kicad_sch_file(filepath)` | `(bool, str)` |

Validators for KiCad s-expression schematic files, per the KiCad schematic file format (see `kicad_docs/sexpr-schematic.md`).  Parsing uses a vendored S-expression parser copied from the MIT-licensed `kicad-tools` project (Copyright (c) 2024 RJ Walters); the validation profile mirrors the minimal schematic shape produced by the MIT-licensed `KiCAD-MCP-Server` project.

- **Header** requires the root to be `kicad_sch` with exactly one `version` (YYYYMMDD date format), `generator`, `uuid`, and `paper` section.  Files that do not parse as valid S-expressions report a spacing error instead.
- **Spacing** validates the whole file as well-formed KiCad S-expressions (balanced parentheses, terminated quoted strings, no trailing content).
- **Footer** requires the closing region: a `sheet_instances` section with at least one `path` entry beginning with `/`, and a final nonblank line ending with the root's closing `)`.
- **Whole-file** composes header, spacing, and footer validators.

Error messages follow the same contract as ASC validation: `"File not found!"`, `"No permission to read file!"`, or `"<type> information is invalid! Line <n>"`.

### KiCad Symbol Validation

| Function | Returns |
|---|---|
| `is_valid_kicad_symbol_file(filepath)` | `(bool, str)` |

Validator for KiCad s-expression symbol library (`.kicad_sym`) files, per the KiCad symbol library file format (see `kicad_docs/sexpr-symbol-lib.md`).  Parsing uses the same vendored S-expression parser as the schematic validators; the validation profile mirrors the symbol shape produced by the MIT-licensed `kicad-tools` project (`kicad_tools.schema.library`) and the MIT-licensed `KiCAD-MCP-Server` project (`SymbolCreator`).

- **Header** requires the root to be `kicad_symbol_lib` with exactly one `version` (YYYYMMDD date format) and exactly one `generator` section.
- **Spacing** validates the whole file as well-formed KiCad S-expressions (balanced parentheses, terminated quoted strings, no trailing content).
- **Symbols** requires at least one top-level `symbol`; each symbol must carry a nonempty name, `in_bom` and `on_board` flags set to `yes` or `no`, and the mandatory `Reference`, `Value`, `Footprint`, and `Datasheet` properties.  Every `pin` must have a valid electrical type and graphic style, an `at` position (X/Y plus optional angle), a numeric `length`, and `name`/`number` labels.
- **Footer** requires the final nonblank line to end with the root's closing `)`.

Error messages follow the same contract as ASC validation: `"File not found!"`, `"No permission to read file!"`, `"Header information is invalid! Line <n>"`, `"Line format/spacing is invalid! Line <n>"`, `"Symbol information is invalid! Line <n>"`, or `"Footer information is invalid! Line <n>"`.

### Netlist Validation

| Function | Returns |
|---|---|
| `is_valid_ltspice_netlist_format(filepath)` | `(bool, str)` |
| `is_valid_ltspice_netlist_footer(filepath)` | `(bool, str)` |
| `is_ltspice_netlist_structure_connected(filepath)` | `(bool, str)` |
| `is_valid_ltspice_netlist_file(filepath)` | `(bool, str)` |

- **Format** checks line classification (device prefixes, dot directives, continuations, comments) and minimum token counts.
- **Footer** requires at least one analysis directive, final line `.end`, penultimate line `.backanno`.
- **Connected** ensures every non-ground, non-`NC*` node appears on at least two device ports.
- **Whole-file** composes the three validators above.

### ASY Validation & Info

| Function | Returns |
|---|---|
| `is_valid_ltspice_asy(filepath)` | `(bool, str)` |
| `get_ltspice_asy_size(filepath)` | `np.ndarray([[min_x, min_y], [max_x, max_y]])` |
| `get_ltspice_asy_pins(filepath)` | `[[x, y, "PinName", spice_order], ...]` |

`get_ltspice_asy_size` and `get_ltspice_asy_pins` raise `ValueError` on invalid inputs.

### Netlist Comparison

| Function | Returns |
|---|---|
| `ltspice_netlist_footer_cmp(filepath1, filepath2)` | `bool` |
| `ltspice_netlist_structure_cmp(filepath1, filepath2)` | `bool` |

- **Footer comparison** normalizes the post-device footer region and checks equivalence.
- **Structure comparison** builds isomorphic component-to-net graphs; ignores instance names, net names, and footer directives.

### Schematic Comparison

| Function | Returns |
|---|---|
| `ltspice_asc_structure_cmp(filepath1, filepath2, convert_settings)` | `(bool, str, int)` |

Converts both ASC files to netlists and compares their structure.  Returns `(True, "", 0)` on match or `(False, "ASC structures are different!", <line>)` on mismatch.

### Schematic Conversion

| Function | Returns |
|---|---|
| `ltspice_asc_to_netlist(asc_filepath, net_filepath_out, convert_settings)` | `(bool, str, int)` |
| `get_ltspice_asc_symbol_info(asc_filepath, convert_settings)` | `{instance_name: {SYMBOL, X, Y, ORIENTATION, RECTANGLE, PINS, ...}, ...}` |
| `ltspice_netlist_to_asc(netlist_filepath, asc_filepath_out, convert_settings)` | `(bool, str, int)` |
| `ltspice_netlist_symbol_wire_to_asc(netlist_filepath, symbol_pose_filepath, wire_filepath, asc_filepath_out, convert_settings)` | `(bool, str, int)` |
| `kicad_sch_to_ltspice_netlist(kicad_sch_filepath, ltspice_netlist_filepath_out, convert_settings)` | `(bool, str, int)` |
| `ltspice_netlist_to_kicad_sch(ltspice_netlist_filepath, kicad_sch_filepath_out, convert_settings)` | `(bool, str, int)` |

- `ltspice_asc_to_netlist` resolves symbols and library files from `convert_settings`, generates a validated netlist. Error codes include `UNKNOWN_SYMBOL`, `UNCONNECTED_SYMBOL_PIN`, `INVALID_GENERATED_NETLIST`, etc.
- `get_ltspice_asc_symbol_info` returns absolute-coordinate symbol pin and rectangle data keyed by instance name. Raises `ValueError` on failure.
- `ltspice_netlist_to_asc` runs the public netlist-to-symbol-initial, autoplace, and netlist/symbol/wire-to-ASC stages to generate one validated schematic from a netlist.
- `ltspice_netlist_symbol_wire_to_asc` reconstructs one LTspice schematic from a netlist, resolved symbol-pose JSON, and routed wire JSON. The generated `.asc` file is written in Latin-1 encoding.
- `kicad_sch_to_ltspice_netlist` converts one KiCad schematic (`.kicad_sch`) into one validated LTspice netlist (`.net`). Symbol definitions, pin geometry, and simulation attributes are looked up from the KiCad symbol libraries under `convert_settings["kicad_path"]` (falling back to the schematic's embedded `lib_symbols` definitions). Power symbols become LTspice voltage sources named after their reference designator (without the leading `#`) with the symbol value as the DC payload; `GND`/`0` power symbols become node `0`. Inductors receive LTspice's standard `Rser=1m` default and three-pin BJT/MOSFET symbols receive the substrate node `0`, matching LTspice's own netlist generator. Pin order follows the symbol's `Sim.Pins` role mapping when present and ascending pin numbers otherwise. Error codes include `INVALID_KICAD_SCH_FILE`, `KICAD_SCH_READ_ERROR`, `KICAD_SCH_PARSE_ERROR`, `UNKNOWN_KICAD_SYMBOL`, `UNCONNECTED_SYMBOL_PIN`, `MISSING_COMPONENT_PAYLOAD`, and `INVALID_GENERATED_NETLIST`.
- `ltspice_netlist_to_kicad_sch` converts one validated LTspice netlist (`.net`) into one validated KiCad schematic (`.kicad_sch`). Every device resolves to a symbol from the KiCad symbol libraries under `convert_settings["kicad_path"]` (resistors, capacitors, and inductors map to the `Device` library; transistors, diodes, and sources to the `Simulation_SPICE` library symbols whose `Sim.Device`/`Sim.Pins` attributes match the netlist device class). When no library symbol matches, the device's LTspice `.asy` file is searched under the configured `custom_search_paths`, `ltspice_wine_path`, and `ltspice_windows_path` roots and converted through the public `ltspice_asy_to_kicad_symbol` API. For `X` subcircuit devices that still resolve to nothing, a validated `.kicad_sym` library file is dynamically generated on the fly: ports are classified by their connected net names (ground and supply nets become `power_in` pins on the bottom and top, `*OUT*` nets become `output` pins on the right, everything else becomes `input` pins on the left), the body is an op-amp-style triangle, and a `REF` circle block with a `REF` text label is added when a `REF`-named port exists. Independent voltage sources remain explicit two-pin simulation symbols, waveform sources prefer their matching `VPULSE`/`VSIN`/other waveform symbol, and the global ground net receives a `GND` power symbol. Component bodies are placed by the default human-style signal-flow layout (or the configured force-directed/evolutionary/hybrid engine when `kicad_placement_strategy` is set), then every ordinary net is physically routed pin-to-pin by a Numba-compiled grid A* router. Hard ownership prevents foreign-net overlap; verified straight-through soft crossings and isolated per-net physical trunk fallbacks handle congested layouts without replacing the full schematic with disconnected labels or global fallback trunks. A final collision-aware text pass positions visible references and values away from symbol bodies, routed wires, page edges, and previously placed text. Authored semantic net labels remain electrically attached for round-trip node identity, while auto-numbered internal nets and ground rely on their physical copper. Embedded symbol pin-name/number annotations are also hidden to prevent text over compact graphics. The generated schematic embeds every resolved symbol definition in its `lib_symbols` section, so `kicad_sch_to_ltspice_netlist` can convert it back without extra files and `ltspice_netlist_structure_cmp` reports structural equivalence with the original netlist. The placement and routing cores are self-contained adaptations of the MIT-licensed `kicad-tools` project (`optim` and `router`, Copyright (c) 2024 RJ Walters). Error codes include `INVALID_CONVERT_SETTINGS`, `INVALID_NETLIST_FILE`, `NETLIST_READ_ERROR`, `UNKNOWN_SYMBOL`, `UNSUPPORTED_DEVICE`, `MISSING_COMPONENT_PAYLOAD`, `INVALID_OUTPUT_PATH`, `WRITE_ERROR`, and `INVALID_GENERATED_KICAD_SCH`.

### LTspice ASY to KiCad Symbol Conversion

| Function | Returns |
|---|---|
| `ltspice_asy_to_kicad_symbol(ltspice_asy_filepath, kicad_symbol_filepath_out, convert_settings)` | `(bool, str, int)` |

Converts one LTspice symbol (`.asy`) file into one self-contained KiCad symbol library (`.kicad_sym`) file. The generated library defines a single top-level symbol named after the ASY file stem, mirroring the symbol shape produced by the MIT-licensed `kicad-tools` project (`kicad_tools.schema.library`) and the MIT-licensed `KiCAD-MCP-Server` project (`SymbolCreator`); no third-party packages are required.

Conversion rules:

- **Graphics** — `LINE` records become `polyline` elements, `RECTANGLE` become `rectangle` (filled `background`), `CIRCLE` become `circle`, and `ARC` become `arc` records with a computed counterclockwise midpoint. Normal/`Wide` strokes map to 0.254/0.508 mm widths.
- **Pins** — every `PIN`/`PINATTR` pair becomes a `pin` with the KiCad pin number, name, and a 2.54 mm default length. The electrical type is guessed from the pin name: power names (`VCC`, `GND`, `+`, …) become `power_in`, `OUT*` names become `output`, `IN*`/`G`/`B`/`EN` names become `input`, and everything else is `passive`.
- **Orientation** — the pin angle follows the ASY `PIN` justification (`LEFT`→0, `RIGHT`→180, `TOP`→270, `BOTTOM`→90), falling back to the nearest body-bounding-box side for `NONE` pins.
- **Attributes** — `SYMATTR Prefix` maps onto the KiCad `Reference` prefix (`X`→`U`, `M`/`MN`/`MP`→`Q`, …), `SYMATTR Value` fills the `Value` property, and `SYMATTR Description` fills the `Description` property. `Footprint` and `Datasheet` properties default to `""` and `"~"`.
- **Coordinates** — 16 LTspice units map to 1.27 mm with the Y axis flipped, and the finished symbol is centered on the 1.27 mm grid.
- **Validation** — the written file is checked with `is_valid_kicad_symbol_file()` before `OK` is returned.

Optional `convert_settings` keys: `kicad_symbol_version` (YYYYMMDD, default today's date), `kicad_symbol_generator` (default `"electronics_design"`), `kicad_symbol_default_footprint` (default `""`), `kicad_symbol_default_datasheet` (default `"~"`), and `kicad_symbol_pin_length` (default `2.54`).

Error codes include `INVALID_CONVERT_SETTINGS`, `INVALID_ASY_FILE`, `ASY_PARSE_ERROR`, `INVALID_OUTPUT_PATH`, `WRITE_ERROR`, and `INVALID_GENERATED_KICAD_SYMBOL`.

### KiCad Schematic to KiCad PCB Conversion

| Function | Returns |
|---|---|
| `kicad_sch_to_kicad_pcb(kicad_sch_filepath, kicad_pcb_filepath_out, convert_settings)` | `(bool, str, int)` |

Converts one KiCad schematic (`.kicad_sch`) into one KiCad board (`.kicad_pcb`) file, using the MIT-licensed `kicad-tools` project (https://github.com/rjwalters/kicad-tools, a declared package dependency in `pyproject.toml`) for the PCB data model, footprint generation, and grid A* autorouting.

Conversion stages:

1. **Parse** — the schematic is validated with `is_valid_kicad_sch_file()` and parsed with the package's vendored S-expression parser.
2. **Trace connectivity** — wires, junctions, labels, no-connect markers, and power symbols are merged with the same union-find tracing used by `kicad_sch_to_ltspice_netlist`; every placed pin resolves to one named net (labels and power values first, then KiCad-style `Net-(REF-PadN)` names).
3. **Resolve footprints** — every non-power component resolves to one footprint: the `kicad_pcb_footprint_map` override (matched by `lib_id`, reference, or reference prefix), the instance or library `Footprint` property, the prefix default table (`kicad_pcb_default_footprints` on top of built-in defaults), or a dynamically generated parametric fallback footprint (chip 2-pin, SOT-23 3-pin, SOIC even 8-32, pin-header otherwise).  Footprint files resolve under `kicad_path/footprints/<Lib>.pretty/<Name>.kicad_mod` and any configured `kicad_pcb_footprint_search_paths`.
4. **Place** — `kicad_pcb_placement_strategy` selects `schematic` (default; scales the schematic signal-flow layout onto the board) or `rows` (deterministic row packing); overlapping bodies are pushed apart, positions snap to a 0.1 mm grid, and the outline grows (or the drawing scales) to fit.
5. **Assemble** — nets are declared, footprints are placed from their `.kicad_mod` files, and every traced net is assigned to its matching pad (direct pin-number match first, positional fallback when the pad and pin counts match).
6. **Route** — every ordinary net is autorouted by the kicad-tools grid A* router with the configured track width, clearance, grid resolution, and via sizes; routed segments and vias are written back through the PCB model.  Because the generated boards carry no copper pours, power nets such as `GND` are routed as ordinary signals; pass `kicad_pcb_skip_route_nets` to exclude plane nets instead.
7. **Validate** — the finished board is reloaded, the placed references are checked, and every routed net's copper connectivity is audited with the kicad-tools connectivity checker.

Error codes include `INVALID_CONVERT_SETTINGS`, `INVALID_OUTPUT_PATH`, `KICAD_TOOLS_UNAVAILABLE`, `INVALID_KICAD_SCH_FILE`, `KICAD_SCH_READ_ERROR`, `KICAD_SCH_PARSE_ERROR`, `UNKNOWN_KICAD_SYMBOL`, `FOOTPRINT_NOT_FOUND`, `PCB_BUILD_FAILED`, `WRITE_ERROR`, `ROUTING_FAILED`, and `INVALID_GENERATED_KICAD_PCB`.  Conversion succeeds even when the autorouter leaves dense nets unrouted; set `kicad_pcb_require_complete_routing` to `True` to turn partial routing into a `ROUTING_FAILED` result.

Optional `convert_settings` keys (all validated by `INVALID_CONVERT_SETTINGS` when malformed):

```python
convert_settings.update({
    "kicad_pcb_layers": 2,               # 2 or 4 copper layers.
    "kicad_pcb_paper": "A4",             # Drawing-sheet size for the board file.
    "kicad_pcb_width": None,             # Explicit outline width in mm (default: auto-sized).
    "kicad_pcb_height": None,            # Explicit outline height in mm (default: auto-sized).
    "kicad_pcb_margin": 5.0,             # Content-to-edge margin in mm.
    "kicad_pcb_title": "",               # Title-block title (default: input file stem).
    "kicad_pcb_placement_strategy": "schematic",  # "schematic" or "rows".
    "kicad_pcb_footprint_map": {},       # {"pattern": "Lib:Footprint"} overrides (lib_id, reference, or prefix).
    "kicad_pcb_default_footprints": {},  # {"prefix": "Lib:Footprint"} overriding built-in defaults.
    "kicad_pcb_footprint_search_paths": [],  # Extra roots scanned for .pretty libraries and .kicad_mod files.
    "kicad_pcb_route_traces": True,      # Set False to emit the placed board without copper.
    "kicad_pcb_track_width": 0.25,       # Routed trace width in mm.
    "kicad_pcb_clearance": 0.2,          # Trace clearance in mm.
    "kicad_pcb_grid_resolution": 0.1,    # Routing grid resolution in mm.
    "kicad_pcb_via_diameter": 0.7,       # Routed via diameter in mm.
    "kicad_pcb_via_drill": 0.35,         # Routed via drill in mm.
    "kicad_pcb_routing_timeout": 300.0,  # Wall-clock routing budget in seconds.
    "kicad_pcb_skip_route_nets": [],     # Net names to leave unrouted (e.g. ["GND"]).
    "kicad_pcb_require_complete_routing": False,  # Fail with ROUTING_FAILED on any unrouted net.
})
```

### Schematic Plotting

| Function | Returns |
|---|---|
| `ltspice_netlist_plot_networkx(netlist_filepath, networkx_imagepath_out, width=1920, height=1080)` | `(bool, str)` |

Uses networkx to render netlist graphs. Supports `.png`, `.svg`, `.jpg`, `.jpeg` output.

### Symbol Pose Pipeline

| Function | Returns |
|---|---|
| `ltspice_netlist_to_symbol_initial(netlist_filepath, symbol_json_filepath_out, convert_settings)` | `(bool, str, int)` |
| `ltspice_resolve_symbol_pose(symbol_json_filepath, convert_settings)` | `(bool, str, int)` |
| `ltspice_check_symbol_pose(symbol_json_filepath, convert_settings)` | `(bool, np.ndarray | None)` |
| `ltspice_symbol_facing(symbol_pose_filepath, convert_settings)` | `{instance_name: [[x, y, pin_name, spice_order, facing], ...], ...}` |
| `ltspice_symbol_estimate(symbol_pose_filepath, core_symbol_name, core_symbol_pin_id, supporting_symbol_name, supporting_symbol_pin_id, convert_settings)` | `{supporting_symbol_name: {SYMBOL, X, Y, ORIENTATION, RECTANGLE, PINS, ...}}` |
| `ltspice_netlist_to_wiring(netlist_filepath, symbol_pose_filepath, wire_filepath_out, convert_settings)` | `(bool, str, int)` |
| `ltspice_netlist_symbol_wire_to_asc(netlist_filepath, symbol_pose_filepath, wire_filepath, asc_filepath_out, convert_settings)` | `(bool, str, int)` |
| `ltspice_autoplace_symbol_pose(netlist_filepath, symbol_pose_filepath_out, wire_filepath_out, convert_settings)` | `(bool, str, int)` |
| `ltspice_netlist_to_asc(netlist_filepath, asc_filepath_out, convert_settings)` | `(bool, str, int)` |

Typical pipeline:

1. **netlist → symbol_initial** — generates JSON with `SYMBOL`, `X=0`, `Y=0`, `ORIENTATION=""`, empty `RECTANGLE` and `PINS`.
2. **resolve_symbol_pose** — populates `RECTANGLE` and `PINS` from `.asy` files using `X`, `Y`, and `ORIENTATION`.
3. **symbol_facing** — derives the outward-facing side of each resolved pin as `+X DIRECTION`, `-X DIRECTION`, `+Y DIRECTION`, or `-Y DIRECTION`.
4. **symbol_estimate** — estimates one supporting symbol pose around one fixed core symbol by choosing `R0/R90/R180/R270`, aligning the requested support pin opposite the core pin facing, and enforcing `minimum_dist` without colliding with the core symbol.
5. **check_symbol_pose** — detects symbol-rectangle collisions after buffering by `minimum_dist`. Returns `(False, None)` or `(True, collisions_array)`.
6. **netlist_to_wiring** — routes axis-aligned wires between symbol pins while avoiding obstacles.
7. **netlist_symbol_wire_to_asc** — converts the netlist, final symbol-pose JSON, and wire JSON back into one LTspice `.asc` file.
8. **autoplace_symbol_pose** — automatically places symbols using a spring-layout-like algorithm, resolves poses, avoids collisions, and generates wiring.
9. **netlist_to_asc** — runs the public netlist-to-symbol-initial, autoplace, and netlist/symbol/wire-to-ASC stages and writes one LTspice `.asc` file directly from a netlist.

### Wire / Path Utilities

| Function | Returns |
|---|---|
| `are_wires_connected(wires)` | `bool` |
| `are_wires_horizontal_or_vertical(wires)` | `bool` |
| `are_wires_intersecting_obstacles_fast(wires, obstacles)` | `bool` |
| `are_wires_intersecting_obstacles_detailed(wires, obstacles)` | `(bool, np.ndarray \| None)` |
| `place_wires_into_groups(wires)` | `list[np.ndarray]` |
| `get_wire_pos(wires)` | `np.ndarray shape (2N, 2)` |
| `find_wire_group_index(point, wire_groups)` | `int` |
| `rectangle_points_to_lines(points)` | `np.ndarray shape (4, 4)` |

All wire/obstacle arrays are numpy arrays of shape `(N, 4)` with rows `[X1, Y1, X2, Y2]`.  Many raise `ValueError` on invalid input shapes.

- `place_wires_into_groups` groups wires that share an exact endpoint.
- `find_wire_group_index` returns the group index containing a point, or -1 if not found.
- `rectangle_points_to_lines` converts two opposite corner points into four edge segments: top, right, left, bottom.

### Autorouting

| Function | Returns |
|---|---|
| `auto_route_wires(start_x, start_y, end_x, end_y, obstacles, grid_x, grid_y)` | `np.ndarray shape (M, 4)` |

Routes an orthogonal, connected wire path between two points on a grid while avoiding obstacle lines. Raises `ValueError` if no valid route exists.

### GUI Debug

| Function | Returns |
|---|---|
| `gui_debug()` | `None` |

Launches a Tkinter path-tracing GUI for interactive wire, obstacle, and flag placement with autorouting preview.

## Return Conventions

Validation and plotting functions return `(True, "")` or `(False, "<error message>")`.

Conversion functions return `(True, "OK", 0)` or `(False, "<error code>", <line number>)`.

Comparison functions return `True` / `False` (netlist) or `(bool, str, int)` (ASC).

## `convert_settings`

A `Mapping` of configuration values used by conversion and pose functions.  Common keys:

```python
convert_settings = {
    # LTspice library search paths (required for ASC/netlist conversion)
    "ltspice_windows_path": "C:\\users\\brosnan\\AppData\\Local\\LTspice\\",
    "ltspice_wine_path": "~/.wine/drive_c/users/brosnan/AppData/Local/LTspice/",
    "custom_search_paths": ["./valid_asy/"],

    # Wiring and pose layout parameters
    "minimum_dist": 32,
    "wire_pin_out_dist": 16,
    "grid_size": 16,
    "autoplace_iter": 12,
    "ltspice_version": 4.1,
    "parallel_workers": 8,
    "voltage_must_have_dc": False,

    # KiCad symbol library path (required for KiCad schematic conversion)
    "kicad_path": "/usr/share/kicad/",

    # KiCad schematic generation (optional, used by ltspice_netlist_to_kicad_sch)
    "kicad_sch_version": "20260306",
    "kicad_sch_generator": "electronics_design",
    "kicad_sch_grid": 1.27,
    "kicad_placement_strategy": "flow",  # flow, physics, evolutionary, or hybrid
    "kicad_placement_iterations": 250,
    "kicad_evolutionary_population": 10,
    "kicad_evolutionary_generations": 6,
    "kicad_placement_seed": 0,
    "kicad_routing_trials": 3,
    "kicad_trace_optimization_passes": 8,
    "kicad_sch_page_width": 297.0,
    "kicad_sch_page_height": 210.0,
}
```

No hard-coded paths are permitted in `src/`; all search paths must be supplied through this mapping.

The KiCad layout dimensions are finite positive millimetre values. When no explicit `kicad_sch_page_width`/`kicad_sch_page_height` is supplied, schematics with more than 80 symbols use A3 landscape (420 x 297 mm) instead of the A4 default, and the finished drawing is translated so every wire, symbol, and label stays inside the page; when the routed content cannot fit A4 or A3 at any edge margin, the paper grows along the A2/A1/A0 ladder instead of letting wires run off the sheet, and the simulation directives stack just above the drawing top. `kicad_placement_strategy` selects the default human-style signal-flow (`flow`) layout, force physics, deterministic evolutionary placement, or the hybrid global-search/physics-refinement pipeline. The `flow` strategy classifies devices by netlist role (source, series, shunt, active), layers nets into signal-flow columns with a longest-path layering over a BFS-distance-directed graph, orders rows with a barycenter sweep, and chooses orientations from role templates using resolved symbol pin geometry; sources sit on the left, signal flows left to right, supply shunts face up, and ground shunts face down. Column gaps tighten to two grid units between columns that hold only horizontal passives, mirroring the density of hand-drawn chains; small two-dimensional plans are stretched by scaling the gaps (never body sizes) so they do not bunch in a corner, while single-line chains keep their compact pitch; and a compact-pitch retry (two-grid column and one-grid row gaps) runs once before an oversized plan falls back. Same-symbol actives sharing an input net are drawn as mirrored differential pairs, and a role-consistent refinement flips only passive devices (series {0,180}, shunts {90,270}) when the pin-to-peer Manhattan distance drops by at least 20%, keeping active devices upright. Dense or oversized plans that cannot fit the A4 page even after the compact retry fall back deterministically to the hybrid engine while remaining electrically equivalent. `kicad_placement_iterations` is a non-negative physics iteration limit; zero skips physics steps while retaining grid snapping. Evolutionary population, generation, and seed settings bound and reproduce the genetic search. The router evaluates two column-aware net orders (fewest then most pins per column) plus a generic small-first candidate within one to three complete net-order trials and keeps the route with the fewest physical fallback trunks, then the shortest trace length and fewest segments; after the chosen trial, nets whose pins all share one horizontal line are rebuilt as single straight wires whenever that keeps every net electrically isolated, and ground-stub fallbacks search all four directions at shrinking lengths so they can never short straightened copper. Trace optimization deduplicates wires and merges only collinear degree-two chains while retaining pin terminals and branch junctions. The grid controls both final component snapping and wire routing resolution; the page width and height bound placement and the primary A* routing area.

CPU-bound geometry and routing kernels use Numba. Independent route candidates,
visibility groups, and symbol-file discovery use bounded thread pools. The
default worker count is the smaller of the available CPU count and eight; set
the `ELECTRONICS_DESIGN_PARALLEL_WORKERS` environment variable to override it.

Set `voltage_must_have_dc` to `True` to normalize AC-only independent voltage sources during netlist-to-symbol/ASC conversion. For example, `V1 IN 0 AC 2` is treated as `V1 IN 0 0 AC 2`. The default is `False`, which preserves existing behavior.

## Install For Local Development

```bash
python3 -m venv .venv
.venv/bin/python -m pip install "kicad-tools[all]>=0.20.0" "numba>=0.63.0" "networkx>=3.6.1" "numpy<2.5" "Pillow>=10.0.0"
```

Run tests:

```bash
PYTHONPATH=src .venv/bin/python -m unittest discover -s tests
```

Or the sequential runner:

```bash
PYTHONPATH=src .venv/bin/python scripts/run_all_tests.py
```

## CLI Usage

# Convert a KiCad schematic to a KiCad PCB
PYTHONPATH=src .venv/bin/python scripts/kicad_sch_to_kicad_pcb.py input.kicad_sch --out output.kicad_pcb

# Render a netlist to a network graph
PYTHONPATH=src .venv/bin/python scripts/ltspice_net_to_networkxpng.py input.net output.svg --width 1600 --height 900

# Convert ASC to netlist
PYTHONPATH=src .venv/bin/python scripts/ltspice_asc_to_netlist.py input.asc

# Extract symbol info from ASC
PYTHONPATH=src .venv/bin/python scripts/ltspice_asc_symbol_info.py input.asc

# Generate wiring from a netlist and symbol-pose JSON
PYTHONPATH=src .venv/bin/python scripts/ltspice_netlist_to_wiring.py input.net symbols.json
```

## Example Usage

```python
import numpy as np
from electronics_design import auto_route_wires
from electronics_design import find_wire_group_index
from electronics_design import get_ltspice_asc_symbol_info
from electronics_design import get_ltspice_asy_pins
from electronics_design import get_ltspice_asy_size
from electronics_design import get_wire_pos
from electronics_design import gui_debug
from electronics_design import is_ltspice_netlist_structure_connected
from electronics_design import is_valid_ltspice_asc_file
from electronics_design import is_valid_ltspice_asc_footer
from electronics_design import is_valid_ltspice_asc_header
from electronics_design import is_valid_ltspice_asc_spacing
from electronics_design import is_valid_ltspice_asy
from electronics_design import is_valid_kicad_sch_file
from electronics_design import is_valid_kicad_sch_footer
from electronics_design import is_valid_kicad_sch_header
from electronics_design import is_valid_kicad_sch_spacing
from electronics_design import is_valid_kicad_symbol_file
from electronics_design import is_valid_ltspice_netlist_file
from electronics_design import is_valid_ltspice_netlist_footer
from electronics_design import is_valid_ltspice_netlist_format
from electronics_design import ltspice_asc_structure_cmp
from electronics_design import ltspice_asc_to_netlist
from electronics_design import ltspice_autoplace_symbol_pose
from electronics_design import ltspice_check_symbol_pose
from electronics_design import ltspice_netlist_to_asc
from electronics_design import ltspice_netlist_footer_cmp
from electronics_design import ltspice_netlist_plot_networkx
from electronics_design import ltspice_netlist_structure_cmp
from electronics_design import ltspice_netlist_to_symbol_initial
from electronics_design import ltspice_netlist_symbol_wire_to_asc
from electronics_design import ltspice_netlist_to_wiring
from electronics_design import ltspice_resolve_symbol_pose
from electronics_design import ltspice_symbol_estimate
from electronics_design import rectangle_points_to_lines
from electronics_design import ltspice_asy_to_kicad_symbol
from electronics_design import kicad_sch_to_ltspice_netlist
from electronics_design import kicad_sch_to_kicad_pcb
from electronics_design import ltspice_netlist_to_kicad_sch
from electronics_design.pathtracing import are_wires_connected
from electronics_design.pathtracing import are_wires_horizontal_or_vertical
from electronics_design.pathtracing import are_wires_intersecting_obstacles_fast
from electronics_design.pathtracing import are_wires_intersecting_obstacles_detailed
from electronics_design.pathtracing import place_wires_into_groups

convert_settings = {
    "ltspice_windows_path": "C:\\users\\brosnan\\AppData\\Local\\LTspice\\",
    "ltspice_wine_path": "~/.wine/drive_c/users/brosnan/AppData/Local/LTspice/",
    "custom_search_paths": ["./valid_asy/"],
    "minimum_dist": 32,
    "wire_pin_out_dist": 16,
    "grid_size": 16,
    "autoplace_iter": 12,
    "ltspice_version": 4.1,
    "parallel_workers": 8,
    "voltage_must_have_dc": False,
    "kicad_path": "/usr/share/kicad/",
    "kicad_sch_version": "20260306",
    "kicad_sch_generator": "electronics_design",
}

# KiCad schematic to KiCad PCB conversion (uses the kicad-tools dependency)
pcb_ok, _, _ = kicad_sch_to_kicad_pcb("example.kicad_sch", "example.kicad_pcb", convert_settings)

# ASC validation
header_ok, _ = is_valid_ltspice_asc_header("example.asc")
spacing_ok, _ = is_valid_ltspice_asc_spacing("example.asc")
footer_ok, _ = is_valid_ltspice_asc_footer("example.asc")
asc_ok, _ = is_valid_ltspice_asc_file("example.asc")

# Netlist validation
fmt_ok, _ = is_valid_ltspice_netlist_format("example.net")
net_footer_ok, _ = is_valid_ltspice_netlist_footer("example.net")
conn_ok, _ = is_ltspice_netlist_structure_connected("example.net")
net_ok, _ = is_valid_ltspice_netlist_file("example.net")

# KiCad schematic validation
kicad_header_ok, _ = is_valid_kicad_sch_header("example.kicad_sch")
kicad_spacing_ok, _ = is_valid_kicad_sch_spacing("example.kicad_sch")
kicad_footer_ok, _ = is_valid_kicad_sch_footer("example.kicad_sch")
kicad_sch_ok, _ = is_valid_kicad_sch_file("example.kicad_sch")

# KiCad symbol validation
kicad_symbol_ok, _ = is_valid_kicad_symbol_file("example.kicad_sym")

# LTspice ASY to KiCad symbol conversion
ltspice_asy_to_kicad_symbol("example.asy", "example.kicad_sym", convert_settings)

# KiCad schematic to LTspice netlist conversion
kicad_netlist_ok, _, _ = kicad_sch_to_ltspice_netlist("example.kicad_sch", "example.net", convert_settings)

# LTspice netlist to KiCad schematic conversion
netlist_to_sch_ok, _, _ = ltspice_netlist_to_kicad_sch("example.net", "example.kicad_sch", convert_settings)

# ASY
asy_ok, _ = is_valid_ltspice_asy("example.asy")
bounds = get_ltspice_asy_size("example.asy")
pins = get_ltspice_asy_pins("example.asy")

# Plotting
ltspice_netlist_plot_networkx("example.net", "graph.png")

# Conversion
convert_ok, _, _ = ltspice_asc_to_netlist("example.asc", "example.net", convert_settings)
symbol_info = get_ltspice_asc_symbol_info("example.asc", convert_settings)

# ASP comparison
cmp_ok, _, _ = ltspice_asc_structure_cmp("a.asc", "b.asc", convert_settings)
same_structure = ltspice_netlist_structure_cmp("a.net", "b.net")
same_footer = ltspice_netlist_footer_cmp("a.net", "b.net")

# Symbol pose pipeline
ltspice_netlist_to_symbol_initial("example.net", "symbols.json", convert_settings)
ltspice_resolve_symbol_pose("symbols.json", convert_settings)
collides, pairs = ltspice_check_symbol_pose("symbols.json", convert_settings)
pin_facings = ltspice_symbol_facing("symbols.json", convert_settings)
supporting_symbol_pose = ltspice_symbol_estimate(
    "symbols.json",
    "U1",
    5,
    "R1",
    1,
    convert_settings,
)
ltspice_netlist_to_wiring("example.net", "symbols.json", "wires.json", convert_settings)
ltspice_netlist_symbol_wire_to_asc("example.net", "symbols.json", "wires.json", "roundtrip.asc", convert_settings)
ltspice_autoplace_symbol_pose("example.net", "symbols.json", "wires.json", convert_settings)
ltspice_netlist_to_asc("example.net", "autoplace.asc", convert_settings)

# Wire utilities
wires = np.array([[16, 32, 0, 16], [0, 16, 16, 48]])
connected = are_wires_connected(wires)
axis_aligned = are_wires_horizontal_or_vertical(wires)
groups = place_wires_into_groups(wires)
points = get_wire_pos(wires)

obstacles = np.array([[48, 32, 0, 32], [0, 16, 0, 72]])
hits = are_wires_intersecting_obstacles_fast(wires, obstacles)
hits_detailed, hit_pairs = are_wires_intersecting_obstacles_detailed(wires, obstacles)

rect_lines = rectangle_points_to_lines(np.array([[-16, -32], [48, 32]]))
group_idx = find_wire_group_index(np.array([16, 0]), groups)
path = auto_route_wires(0, 0, 128, 128, obstacles, 16, 16)
```

## Package Layout

```text
src/electronics_design/
    __init__.py
    autoroute.py
    force_directed_placement.py
    kicad_sch.py
    kicad_sexp_parser.py
    kicad_sch_to_kicad_pcb.py
    kicad_symbol.py
    ltspice.py
    ltspice_asc.py
    ltspice_asc_to_netlist.py
    ltspice_asy.py
    ltspice_asy_to_kicad_symbol.py
    ltspice_autoplace_symbol_pose.py
    ltspice_net.py
    ltspice_netlist_plot_networkx.py
    ltspice_netlist_to_symbol_initial.py
    ltspice_netlist_to_kicad_sch.py
    ltspice_symbol_estimate.py
    ltspice_netlist_to_wiring.py
    ltspice_resolve_symbol_pose.py
    pathtracing.py
    schematic_grid_router.py
tests/
test_files/
valid_asy/
valid_asc/
valid_netlist/
valid_convert/
scripts/
pyproject.toml
```

## Build And Publish

```bash
.venv/bin/python -m pip install --upgrade build twine
.venv/bin/python -m build
.venv/bin/python -m twine check dist/*
.venv/bin/python -m twine upload dist/*
```

See `SUBMIT.md` for the checklist.
