"""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 "/" (page "")))`` so KiCad can bind it to the root. - With both halves wired up, ``kicad-cli sch erc `` 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="/"`` 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 ``"/"``. """ 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", 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"), 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)