Files
FacereDataset/tools/epro2/kicad/root_sch_writer.py
Knowit 3c00edf6db tools/epro2/kicad: --all emits paired .kicad_pro + .kicad_sch + .kicad_pcb per BOARD
KiCad pairs project files purely by basename + same directory: a folder
holding `Foo.kicad_pro`, `Foo.kicad_sch`, `Foo.kicad_pcb` opens as one
project on double-click of the .kicad_pro, with cross-tool navigation
(open footprint from schematic etc) wired up automatically.

  - pro_writer.write_kicad_pro() renders the minimal KiCad 8 JSON we
    need: meta.filename pinning the basename, sheets=[[<root_uuid>,
    ""]] binding the schematic root, and stub blocks for board /
    schematic / net_settings / erc that KiCad expects to find on the
    first GUI load.
  - root_sch_writer.write_root_sheet() now accepts an optional
    root_uuid so the caller can pass the same uuid into the .kicad_pro
    and .kicad_sch (the binding fails silently with mismatched ids).
  - CLI gains `--all`: groups SCH/PCB docs by their META.board uuid
    (1:1 in EPRO2), strips SCH-/PCB- editor prefixes from titles to
    derive a shared project basename, and emits one directory per
    BOARD with paired files. BOARDs whose SCH is DELETE_DOC (LCD-BD on
    ESP-VoCat) still get a .kicad_pro with sheets:[] + .kicad_pcb so
    pcbnew opens cleanly.

ESP-VoCat smoke: 6 boards → 6 project dirs, all pairs validated by
kicad-cli sch erc / pcb export svg. The CoreBoard pro/sch/pcb trio
shares root uuid 366d3e53...c2fccbe4330b end-to-end.

68 → 71 unit tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 00:39:58 +08:00

127 lines
4.4 KiB
Python

"""Generate a root ``.kicad_sch`` that ties multiple SCH_PAGE files into one
hierarchical project.
EPRO2 multi-page schematics live as N standalone ``SCH_PAGE`` docs all
referencing the same ``SCH`` doc through ``META.schematic``. When we exported
each page as its own ``.kicad_sch`` and ran ``kicad-cli sch erc`` against it
in isolation, every cross-sheet net showed up as ``label_dangling`` /
``pin_not_connected`` because single-sheet ERC can't see the matching
endpoint on a sibling page.
Hierarchical mode fixes that:
- Root .kicad_sch carries one ``(sheet ...)`` block per child page,
each with a uuid the root assigns and a ``Sheetfile`` pointing at the
child filename in the same directory.
- Each child .kicad_sch declares its position in the hierarchy with
``(sheet_instances (path "/<assigned_uuid>" (page "<n>")))`` so KiCad
can bind it to the root.
- With both halves wired up, ``kicad-cli sch erc <root>`` walks the whole
project, resolving global_labels across pages.
Usage:
Build a list of (filename, page_title, assigned_uuid) tuples — one per
child — *before* writing the children, then pass it here to emit the
root. The same uuid you stored in this list goes into the child writer
via its ``sheet_path="/<uuid>"`` argument.
"""
from __future__ import annotations
import uuid as _uuid
from dataclasses import dataclass
from .sexpr import Sym, to_sexpr
KICAD_SCH_VERSION = 20231120
KICAD_GENERATOR = "facere-epro2"
@dataclass
class ChildSheet:
"""One child entry in a root sheet.
``filename`` is relative to the root .kicad_sch's directory.
``sheet_uuid`` is the uuid the root assigns to this child; the child's
own (sheet_instances) must echo it back as ``"/<sheet_uuid>"``.
"""
filename: str
title: str
sheet_uuid: str
def new_sheet_uuid() -> str:
return str(_uuid.uuid4())
def _font(size: float = 1.27) -> list:
return [Sym("effects"), [Sym("font"), [Sym("size"), size, size]],
[Sym("justify"), Sym("left"), Sym("bottom")]]
def _file_font(size: float = 1.27) -> list:
return [Sym("effects"), [Sym("font"), [Sym("size"), size, size]],
[Sym("justify"), Sym("left"), Sym("top")]]
def write_root_sheet(
title: str,
children: list[ChildSheet],
*,
project_name: str = "facere",
root_uuid: str | None = None,
grid_origin_mm: tuple[float, float] = (38.1, 38.1),
box_size_mm: tuple[float, float] = (50.8, 25.4),
box_pitch_mm: tuple[float, float] = (63.5, 38.1),
columns: int = 3,
) -> str:
"""Render a root ``.kicad_sch`` listing ``children`` as ``(sheet ...)`` blocks.
The boxes are laid out on a simple ``columns``-wide grid so they don't
overlap on the canvas; visual layout is irrelevant to ERC but KiCad's
GUI needs non-zero positions/sizes for the sheet to be visible/editable.
"""
elements: list = []
bw, bh = box_size_mm
px, py = box_pitch_mm
ox, oy = grid_origin_mm
for idx, child in enumerate(children):
col = idx % columns
row = idx // columns
x = ox + col * px
y = oy + row * py
page_num = idx + 2 # root is page 1, children start at 2
elements.append([
Sym("sheet"),
[Sym("at"), x, y],
[Sym("size"), bw, bh],
[Sym("stroke"), [Sym("width"), 0.1524], [Sym("type"), Sym("solid")]],
[Sym("fill"), [Sym("color"), 0, 0, 0, 0.0]],
[Sym("uuid"), child.sheet_uuid],
[Sym("property"), "Sheetname", child.title,
[Sym("at"), x, y - 0.5, 0], _font()],
[Sym("property"), "Sheetfile", child.filename,
[Sym("at"), x, y + bh + 0.5, 0], _file_font()],
[Sym("instances"),
[Sym("project"), project_name,
[Sym("path"), "/", [Sym("page"), str(page_num)]]]],
])
sch: list = [
Sym("kicad_sch"),
[Sym("version"), KICAD_SCH_VERSION],
[Sym("generator"), KICAD_GENERATOR],
[Sym("uuid"), root_uuid or new_sheet_uuid()],
[Sym("paper"), "A4"],
[Sym("title_block"),
[Sym("title"), title],
[Sym("comment"), 1, f"hierarchical root for {len(children)} child sheet(s)"]],
[Sym("lib_symbols")],
*elements,
[Sym("sheet_instances"),
[Sym("path"), "/", [Sym("page"), "1"]]],
]
return to_sexpr(sch, pretty=True)