Ingest hierarchical KiCad sheets; still skip .history.
Apri progetto collects every *.kicad_sch beside the .kicad_pro and follows Sheetfile/(sheet)/file= into a subfolder. .history, .git, and backups are not walked. Not only HubAudio.kicad_sch.
This commit is contained in:
@@ -406,3 +406,213 @@ def test_real_hubaudio_recursive_tree_is_huge_project_dir_is_small():
|
||||
assert any(p.name.endswith(".kicad_pcb") for p in project_dir)
|
||||
assert all(".history" not in str(p) for p in project_dir)
|
||||
assert len(project_dir) < 40
|
||||
sch_names = {p.name for p in project_dir if p.suffix.lower() == ".kicad_sch"}
|
||||
assert "HubAudio.kicad_sch" in sch_names
|
||||
assert "USB.kicad_sch" in sch_names
|
||||
assert "Codec.kicad_sch" in sch_names
|
||||
assert "POWER.kicad_sch" in sch_names
|
||||
assert len(sch_names) > 1
|
||||
|
||||
|
||||
def _sheet_block(name: str, file: str) -> str:
|
||||
return f"""
|
||||
(sheet
|
||||
(at 50 0)
|
||||
(size 20 20)
|
||||
(property "Sheetname" "{name}" (at 50 0 0) (effects (font (size 1.27 1.27))))
|
||||
(property "Sheetfile" "{file}" (at 50 0 0) (effects (font (size 1.27 1.27))))
|
||||
)
|
||||
"""
|
||||
|
||||
|
||||
def _write_hier_modules(folder: Path) -> None:
|
||||
"""Root + USB/Codec/POWER siblings + sheets/nested.kicad_sch; junk in .history."""
|
||||
folder.mkdir(parents=True, exist_ok=True)
|
||||
nested_dir = folder / "sheets"
|
||||
nested_dir.mkdir()
|
||||
history = folder / ".history"
|
||||
history.mkdir()
|
||||
(history / "HubAudio.kicad_sch").write_text(_sch(_ic("U99", "FAKE", "FAKE")))
|
||||
(history / "evil.kicad_pro").write_text(_pro("HubAudio.kicad_sch"))
|
||||
usb = _sch(
|
||||
_ic("U2", "CH340E", "CH340E"),
|
||||
"""
|
||||
(global_label "GND" (at 0 3.81 0) (uuid "dddddddd-dddd-dddd-dddd-dddddddddddd"))
|
||||
""",
|
||||
)
|
||||
codec = _sch(
|
||||
_ic("U9", "TPD2E007DCKR", "TPD2E007DCKR"),
|
||||
"""
|
||||
(global_label "GND" (at 0 3.81 0) (uuid "cccccccccccccccccccccccccccccccccccc"))
|
||||
""",
|
||||
)
|
||||
power = _sch(
|
||||
_ic("U1", "SPX3819M5-L-3-3", "SPX3819M5-L-3-3"),
|
||||
"""
|
||||
(global_label "GND" (at 0 3.81 0) (uuid "eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee"))
|
||||
""",
|
||||
)
|
||||
nested = _sch(
|
||||
_ic("U4", "PCA9534ARGTR", "PCA9534ARGTR"),
|
||||
"""
|
||||
(global_label "GND" (at 0 3.81 0) (uuid "ffffffffffffffffffffffffffffffffffffffff"))
|
||||
""",
|
||||
)
|
||||
root = _sch(
|
||||
_resistor("R1", "10k"),
|
||||
"""
|
||||
(global_label "GND" (at 0 3.81 0) (uuid "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"))
|
||||
"""
|
||||
+ _sheet_block("USB", "USB.kicad_sch")
|
||||
+ _sheet_block("Codec", "Codec.kicad_sch")
|
||||
+ _sheet_block("POWER", "POWER.kicad_sch")
|
||||
+ _sheet_block("Nested", "sheets/nested.kicad_sch"),
|
||||
)
|
||||
(folder / "HubAudio.kicad_pro").write_text(_pro("HubAudio.kicad_sch"))
|
||||
(folder / "HubAudio.kicad_sch").write_text(root)
|
||||
(folder / "USB.kicad_sch").write_text(usb)
|
||||
(folder / "Codec.kicad_sch").write_text(codec)
|
||||
(folder / "POWER.kicad_sch").write_text(power)
|
||||
(nested_dir / "nested.kicad_sch").write_text(nested)
|
||||
(folder / "HubAudio.kicad_pcb").write_text(
|
||||
'(kicad_pcb (version 20240108) (generator pcbnew)\n (net 0 "")\n)\n'
|
||||
)
|
||||
|
||||
|
||||
def _sheetfiles_from_text(text: str) -> list[str]:
|
||||
found: list[str] = []
|
||||
for pat in (
|
||||
r'\(\s*property\s+"Sheetfile"\s+"([^"]+)"',
|
||||
r'\(\s*sheetfile\s+"([^"]+)"',
|
||||
r'\(\s*file\s+"([^"]+\.kicad_sch)"',
|
||||
r'file\s*=\s*"([^"]+\.kicad_sch)"',
|
||||
):
|
||||
found.extend(re.findall(pat, text, flags=re.I))
|
||||
return list(dict.fromkeys(found))
|
||||
|
||||
|
||||
def _join_sheet_rel(sch_rel: str, sheetfile: str) -> str | None:
|
||||
file = sheetfile.replace("\\", "/").strip()
|
||||
if not file or file.startswith("/") or ".." in file.split("/"):
|
||||
return None
|
||||
parent = sch_rel.replace("\\", "/").rsplit("/", 1)[0] if "/" in sch_rel.replace("\\", "/") else ""
|
||||
joined = f"{parent}/{file}" if parent else file
|
||||
if _skip_rel(joined):
|
||||
return None
|
||||
return joined
|
||||
|
||||
|
||||
def _files_beside(incoming: list[dict[str, str]]) -> list[dict[str, str]]:
|
||||
"""Same keep rule as frontend filesBesideKicadPro (all sch under project dir)."""
|
||||
hits = [
|
||||
f
|
||||
for f in incoming
|
||||
if _looks_like_kicad_pro(f["name"], f.get("webkitRelativePath", ""))
|
||||
and not _skip_rel(f.get("webkitRelativePath") or f["name"])
|
||||
]
|
||||
if not hits:
|
||||
return []
|
||||
pro_rel = (hits[0].get("webkitRelativePath") or hits[0]["name"]).replace("\\", "/")
|
||||
pro_dir = pro_rel.rsplit("/", 1)[0] if "/" in pro_rel else ""
|
||||
kept: list[dict[str, str]] = []
|
||||
for f in incoming:
|
||||
rel = (f.get("webkitRelativePath") or f["name"]).replace("\\", "/")
|
||||
if _skip_rel(rel):
|
||||
continue
|
||||
parent = rel.rsplit("/", 1)[0] if "/" in rel else ""
|
||||
under = parent == pro_dir or (
|
||||
bool(pro_dir) and (parent == pro_dir or parent.startswith(pro_dir + "/"))
|
||||
) or (not parent and not pro_dir)
|
||||
if not under:
|
||||
continue
|
||||
base = rel.rsplit("/", 1)[-1].lower()
|
||||
if not (
|
||||
".kicad_pro" in rel.lower()
|
||||
or base.endswith((".kicad_sch", ".kicad_pcb"))
|
||||
):
|
||||
continue
|
||||
kept.append(f)
|
||||
return kept
|
||||
|
||||
|
||||
def test_sheetfiles_from_root_text_usb_codec_power():
|
||||
text = (
|
||||
_sheet_block("USB", "USB.kicad_sch")
|
||||
+ _sheet_block("Codec", "Codec.kicad_sch")
|
||||
+ _sheet_block("POWER", "POWER.kicad_sch")
|
||||
+ '(file "sheets/nested.kicad_sch")'
|
||||
+ 'file="also.kicad_sch"'
|
||||
)
|
||||
names = _sheetfiles_from_text(text)
|
||||
assert "USB.kicad_sch" in names
|
||||
assert "Codec.kicad_sch" in names
|
||||
assert "POWER.kicad_sch" in names
|
||||
assert "sheets/nested.kicad_sch" in names
|
||||
assert "also.kicad_sch" in names
|
||||
assert _join_sheet_rel("HubAudio/HubAudio.kicad_sch", "USB.kicad_sch") == "HubAudio/USB.kicad_sch"
|
||||
assert _join_sheet_rel("HubAudio/HubAudio.kicad_sch", "sheets/nested.kicad_sch") == "HubAudio/sheets/nested.kicad_sch"
|
||||
assert _join_sheet_rel("HubAudio/HubAudio.kicad_sch", ".history/x.kicad_sch") is None
|
||||
assert _join_sheet_rel("HubAudio/HubAudio.kicad_sch", "../escape.kicad_sch") is None
|
||||
|
||||
|
||||
def test_keep_every_sibling_sch_not_only_root():
|
||||
incoming = [
|
||||
{"name": "HubAudio.kicad_pro", "webkitRelativePath": "HubAudio/HubAudio.kicad_pro"},
|
||||
{"name": "HubAudio.kicad_sch", "webkitRelativePath": "HubAudio/HubAudio.kicad_sch"},
|
||||
{"name": "USB.kicad_sch", "webkitRelativePath": "HubAudio/USB.kicad_sch"},
|
||||
{"name": "Codec.kicad_sch", "webkitRelativePath": "HubAudio/Codec.kicad_sch"},
|
||||
{"name": "POWER.kicad_sch", "webkitRelativePath": "HubAudio/POWER.kicad_sch"},
|
||||
{"name": "nested.kicad_sch", "webkitRelativePath": "HubAudio/sheets/nested.kicad_sch"},
|
||||
{"name": "HubAudio.kicad_pcb", "webkitRelativePath": "HubAudio/HubAudio.kicad_pcb"},
|
||||
{"name": "HubAudio.kicad_sch", "webkitRelativePath": "HubAudio/.history/HubAudio.kicad_sch"},
|
||||
]
|
||||
kept = _files_beside(incoming)
|
||||
names = { (f.get("webkitRelativePath") or f["name"]).rsplit("/", 1)[-1] for f in kept }
|
||||
rels = { f.get("webkitRelativePath") or f["name"] for f in kept }
|
||||
assert "HubAudio.kicad_sch" in names
|
||||
assert "USB.kicad_sch" in names
|
||||
assert "Codec.kicad_sch" in names
|
||||
assert "POWER.kicad_sch" in names
|
||||
assert "nested.kicad_sch" in names
|
||||
assert names != {"HubAudio.kicad_sch"}
|
||||
assert "HubAudio/.history/HubAudio.kicad_sch" not in rels
|
||||
|
||||
|
||||
def test_hier_modules_and_nested_sheet_history_ignored(tmp_path: Path):
|
||||
src = tmp_path / "HubAudio"
|
||||
_write_hier_modules(src)
|
||||
loaded = load_kicad_project(src)
|
||||
names = {p.name for p in loaded.sheets}
|
||||
assert names == {
|
||||
"HubAudio.kicad_sch",
|
||||
"USB.kicad_sch",
|
||||
"Codec.kicad_sch",
|
||||
"POWER.kicad_sch",
|
||||
"nested.kicad_sch",
|
||||
}
|
||||
assert all(".history" not in p.parts for p in loaded.sheets)
|
||||
assert loaded.pro.name == "HubAudio.kicad_pro"
|
||||
parts, _nets, fmt = parse_netlist_any(loaded.root_sch)
|
||||
assert fmt == "kicad_sch"
|
||||
assert "U2" in parts and "U9" in parts and "U1" in parts and "U4" in parts
|
||||
assert "U99" not in parts
|
||||
files = [
|
||||
(str(p.relative_to(tmp_path)), p.read_bytes())
|
||||
for p in src.rglob("*")
|
||||
if p.is_file() and p.suffix.lower() in {".kicad_pro", ".kicad_sch", ".kicad_pcb"}
|
||||
and ".history" not in p.parts
|
||||
]
|
||||
parsed = materialize_netlist_upload(files, tmp_path / "work")
|
||||
extra = {p.name for p in parsed.extra_sch}
|
||||
assert extra == {"USB.kicad_sch", "Codec.kicad_sch", "POWER.kicad_sch", "nested.kicad_sch"}
|
||||
copied = {p.name for p in (tmp_path / "work").rglob("*.kicad_sch")}
|
||||
assert "nested.kicad_sch" in copied
|
||||
assert not list((tmp_path / "work").rglob(".history/**/*"))
|
||||
|
||||
|
||||
def test_history_kicad_pro_is_not_the_project(tmp_path: Path):
|
||||
src = tmp_path / "HubAudio"
|
||||
_write_hier_modules(src)
|
||||
loaded = load_kicad_project(src)
|
||||
assert ".history" not in loaded.pro.parts
|
||||
assert loaded.pro.parent == src.resolve()
|
||||
Reference in New Issue
Block a user