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>
97 lines
3.2 KiB
Python
97 lines
3.2 KiB
Python
"""Emit a minimal ``.kicad_pro`` so a sibling ``.kicad_sch`` + ``.kicad_pcb``
|
|
pair opens as one project in the KiCad GUI (double-click).
|
|
|
|
KiCad pairs files by basename: a directory containing
|
|
``EchoEar-CoreBoard.kicad_pro`` + ``EchoEar-CoreBoard.kicad_sch`` +
|
|
``EchoEar-CoreBoard.kicad_pcb`` is treated as one project, and opening the
|
|
.kicad_pro launches both editors with cross-navigation working. Without
|
|
this file each .kicad_sch / .kicad_pcb opens as a one-off and KiCad puts
|
|
up a "create project file?" dialog every time.
|
|
|
|
The .kicad_pro is JSON. KiCad will fill in defaults for anything missing
|
|
the next time the GUI opens it; we only emit the keys that meaningfully
|
|
identify the project (``meta``, ``sheets`` for hierarchy root binding,
|
|
empty stubs for ``board`` / ``schematic`` / ``net_settings`` so the
|
|
top-level keys exist in the form KiCad expects).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
|
|
|
|
def write_kicad_pro(
|
|
project_basename: str,
|
|
*,
|
|
root_sheet_uuid: str | None = None,
|
|
) -> str:
|
|
"""Render a JSON .kicad_pro string for one project.
|
|
|
|
``project_basename`` is the filename without extension — e.g.
|
|
``"EchoEar-CoreBoard"``. ``root_sheet_uuid`` is the uuid at the top of
|
|
the sibling .kicad_sch's ``(uuid ...)`` form; KiCad uses it to bind
|
|
the root sheet to the project's hierarchy. If omitted, KiCad
|
|
generates one on first open (works but causes a "save changes?"
|
|
prompt the first time).
|
|
"""
|
|
sheets: list = []
|
|
if root_sheet_uuid:
|
|
sheets.append([root_sheet_uuid, ""])
|
|
|
|
pro: dict = {
|
|
"board": {
|
|
"design_settings": {
|
|
"defaults": {},
|
|
"rules": {},
|
|
},
|
|
"layer_presets": [],
|
|
"viewports": [],
|
|
},
|
|
"boards": [],
|
|
"cvpcb": {"equivalence_files": []},
|
|
"erc": {
|
|
"erc_exclusions": [],
|
|
"meta": {"version": 0},
|
|
"pin_map": [],
|
|
"rule_severities": {},
|
|
"rule_severitieslegacy": {},
|
|
},
|
|
"libraries": {"pinned_footprint_libs": [], "pinned_symbol_libs": []},
|
|
"meta": {
|
|
"filename": f"{project_basename}.kicad_pro",
|
|
"version": 1,
|
|
},
|
|
"net_settings": {
|
|
"classes": [],
|
|
"meta": {"version": 3},
|
|
"net_colors": None,
|
|
"netclass_assignments": None,
|
|
"netclass_patterns": [],
|
|
},
|
|
"pcbnew": {
|
|
"last_paths": {},
|
|
"page_layout_descr_file": "",
|
|
},
|
|
"schematic": {
|
|
"annotate_start_num": 0,
|
|
"bom_export_filename": "",
|
|
"bom_fmt_presets": [],
|
|
"bom_fmt_settings": {},
|
|
"bom_presets": [],
|
|
"bom_settings": {},
|
|
"drawing": {},
|
|
"legacy_lib_dir": "",
|
|
"legacy_lib_list": [],
|
|
"meta": {"version": 1},
|
|
"net_format_name": "",
|
|
"page_layout_descr_file": "",
|
|
"plot_directory": "",
|
|
"spice_external_command": "spice \"%I\"",
|
|
"subpart_first_id": 65,
|
|
"subpart_id_separator": 0,
|
|
},
|
|
"sheets": sheets,
|
|
"text_variables": {},
|
|
}
|
|
return json.dumps(pro, indent=2)
|