Skip to content

Commit 51b96c6

Browse files
authored
Derive layout PCB path from .kicad_pro file (#525)
Previously, we tried to tolerative missing .kicad_pro file by also looking for a unique .kicad_pcb file but this is unnecessary complexity. We can safely assume a .kicad_pro file is present/necessary.
1 parent def667f commit 51b96c6

File tree

4 files changed

+28
-56
lines changed

4 files changed

+28
-56
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ and this project adheres to Semantic Versioning (https://semver.org/spec/v2.0.0.
88

99
## [Unreleased]
1010

11+
### Fixed
12+
13+
- Layout discovery now uses only `.kicad_pro` files, ignoring extra `.kicad_pcb` files in the layout directory.
14+
1115
## [0.3.38] - 2026-02-11
1216

1317
### Added

crates/pcb-layout/src/lib.rs

Lines changed: 14 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -631,7 +631,10 @@ pub mod utils {
631631
}
632632
}
633633

634-
/// Discover KiCad files in a layout directory (errors if ambiguous).
634+
/// Discover KiCad files in a layout directory by finding a single `.kicad_pro` file.
635+
///
636+
/// The `.kicad_pcb` path is derived from the project file name. This avoids
637+
/// false ambiguity from KiCad autosave files like `_autosave-layout.kicad_pcb`.
635638
pub fn discover_kicad_files(layout_dir: &Path) -> anyhow::Result<Option<KiCadLayoutFiles>> {
636639
if !layout_dir.exists() {
637640
return Ok(None);
@@ -641,7 +644,6 @@ pub mod utils {
641644
}
642645

643646
let mut pro_path: Option<PathBuf> = None;
644-
let mut pcb_path: Option<PathBuf> = None;
645647
for entry in fs::read_dir(layout_dir)
646648
.with_context(|| format!("Failed to read {}", layout_dir.display()))?
647649
{
@@ -650,48 +652,23 @@ pub mod utils {
650652
continue;
651653
}
652654
let path = entry.path();
653-
match path.extension().and_then(|s| s.to_str()) {
654-
Some("kicad_pro") => {
655-
if pro_path.replace(path).is_some() {
656-
anyhow::bail!(
657-
"Multiple .kicad_pro files found in {}",
658-
layout_dir.display()
659-
);
660-
}
661-
}
662-
Some("kicad_pcb") => {
663-
if pcb_path.replace(path).is_some() {
664-
anyhow::bail!(
665-
"Multiple .kicad_pcb files found in {}",
666-
layout_dir.display()
667-
);
668-
}
669-
}
670-
_ => {}
655+
if path.extension().and_then(|s| s.to_str()) == Some("kicad_pro")
656+
&& pro_path.replace(path).is_some()
657+
{
658+
anyhow::bail!(
659+
"Multiple .kicad_pro files found in {}",
660+
layout_dir.display()
661+
);
671662
}
672663
}
673664

674-
if let Some(pro_path) = pro_path {
675-
Ok(Some(KiCadLayoutFiles {
676-
kicad_pro: pro_path,
677-
}))
678-
} else if let Some(pcb_path) = pcb_path {
679-
Ok(Some(KiCadLayoutFiles {
680-
kicad_pro: pcb_path.with_extension("kicad_pro"),
681-
}))
682-
} else {
683-
Ok(None)
684-
}
665+
Ok(pro_path.map(|p| KiCadLayoutFiles { kicad_pro: p }))
685666
}
686667

687668
/// Require a discoverable KiCad layout in `layout_dir`.
688669
pub fn require_kicad_files(layout_dir: &Path) -> anyhow::Result<KiCadLayoutFiles> {
689-
discover_kicad_files(layout_dir)?.ok_or_else(|| {
690-
anyhow::anyhow!(
691-
"No .kicad_pro or .kicad_pcb found in {}",
692-
layout_dir.display()
693-
)
694-
})
670+
discover_kicad_files(layout_dir)?
671+
.ok_or_else(|| anyhow::anyhow!("No .kicad_pro file found in {}", layout_dir.display()))
695672
}
696673

697674
/// Resolve target file names for layout generation (defaults to `layout.*`).

crates/pcb-layout/src/scripts/lens/kicad_adapter.py

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -56,33 +56,25 @@
5656
def _discover_kicad_pcb_file(layout_dir: Path) -> Path:
5757
"""Discover the KiCad PCB file inside a layout directory.
5858
59-
Prefers a single top-level `.kicad_pro` and derives `.kicad_pcb` by swapping
60-
extension. Falls back to a single top-level `.kicad_pcb`. Errors on ambiguity.
59+
Finds a single `.kicad_pro` file and derives the `.kicad_pcb` path from it.
60+
This avoids false ambiguity from KiCad autosave files like
61+
``_autosave-layout.kicad_pcb``.
6162
"""
6263
if not layout_dir.is_dir():
6364
raise FileNotFoundError(f"Layout fragment not found: {layout_dir}")
6465

65-
pro_files: List[Path] = []
66-
pcb_files: List[Path] = []
67-
for entry in layout_dir.iterdir():
68-
if not entry.is_file():
69-
continue
70-
if entry.suffix == ".kicad_pro":
71-
pro_files.append(entry)
72-
elif entry.suffix == ".kicad_pcb":
73-
pcb_files.append(entry)
66+
pro_files: List[Path] = [
67+
entry
68+
for entry in layout_dir.iterdir()
69+
if entry.is_file() and entry.suffix == ".kicad_pro"
70+
]
7471

7572
if len(pro_files) > 1:
7673
raise ValueError(f"Multiple .kicad_pro files found in {layout_dir}")
7774
if len(pro_files) == 1:
7875
return pro_files[0].with_suffix(".kicad_pcb")
7976

80-
if len(pcb_files) > 1:
81-
raise ValueError(f"Multiple .kicad_pcb files found in {layout_dir}")
82-
if len(pcb_files) == 1:
83-
return pcb_files[0]
84-
85-
raise FileNotFoundError(f"Layout fragment not found: {layout_dir}")
77+
raise FileNotFoundError(f"No .kicad_pro file found in {layout_dir}")
8678

8779

8880
@dataclass(frozen=True)

crates/pcb/src/release.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -631,8 +631,7 @@ fn discover_layout_from_output(
631631
};
632632
let layout_path = zen_parent_dir.join(clean_path_str);
633633

634-
// Discover KiCad files (accept a single top-level .kicad_pro or .kicad_pcb).
635-
// If there are multiple candidates, treat it as an error (ambiguous).
634+
// Discover KiCad files (require a single top-level .kicad_pro).
636635
let discovered = layout_utils::discover_kicad_files(&layout_path)?;
637636
if discovered.is_none() {
638637
if layout_path.exists() {

0 commit comments

Comments
 (0)