← Commit history

1.0.36: 1.0.36: three more fixes from the full verb battery on a from-scratch Windows VM (KiCad 10.0.6). Stock libraries resolve on a fresh KiCad 10 install: its user sym-lib-table and fp-lib-table hold ONE row of type Table pointing at KiCad's template table, and the bridge only read the top level, so Device and every other stock library looked unregistered (kicad_list_symbols, kicad_sch_place_symbol and kicad_list_footprints now follow the nested table; the writers still never touch that row). kicad_open_symbol_editor and the other tree navigations wait for an editor that is busy loading libraries instead of giving up after 1.5 s. kicad_rescan_libraries gets the same foreground guard as the open verbs, so a footprint that finishes loading late no longer leaves the editor on top of the user's window.

John Lauer ·14e12dec02 ·1mo ago ·parent 26a9e7f
2 files changed +57−2
rust/crates/kicad-core/src/libraries.rs+56−1
@@ -221,6 +221,44 @@ impl LibTable {         LibTable::parse_str(&text).map_err(|e| format!("{}: {e}", path.display()))     } +    /// READ-ONLY view with nested tables expanded. A fresh KiCad 10 install does not list the stock+    /// libraries in the user's table: it writes ONE row of type "Table" whose uri is KiCad's own+    /// template table. Looking only at the top level made every stock library (Device, ...) look+    /// unregistered on a blank box (winvm, 10.0.6). Never write this view back: it would flatten+    /// KiCad's file. Rows of the outer table win over nested rows of the same name.+    pub fn parse_file_resolved(path: &Path) -> Result<LibTable, String> {+        let mut table = LibTable::parse_file(path)?;+        let mut seen = vec![path.to_path_buf()];+        let mut depth = 0;+        while depth < 4 && table.entries.iter().any(|e| e.lib_type.eq_ignore_ascii_case("Table")) {+            depth += 1;+            let mut flat: Vec<LibEntry> = Vec::new();+            let mut nested: Vec<LibEntry> = Vec::new();+            for e in std::mem::take(&mut table.entries) {+                if !e.lib_type.eq_ignore_ascii_case("Table") {+                    flat.push(e);+                    continue;+                }+                let inner = PathBuf::from(e.uri.replace('\\', "/"));+                if e.disabled || seen.contains(&inner) || !inner.is_file() {+                    continue;+                }+                seen.push(inner.clone());+                if let Ok(t) = LibTable::parse_file(&inner) {+                    nested.extend(t.entries);+                }+            }+            for e in nested {+                if !flat.iter().any(|f| f.name == e.name) {+                    flat.push(e);+                }+            }+            table.entries = flat;+        }+        table.entries.retain(|e| !e.lib_type.eq_ignore_ascii_case("Table"));+        Ok(table)+    }+     pub fn parse_str(text: &str) -> Result<LibTable, String> {         let root = parse_sexp(text).map_err(|e| format!("Cannot parse lib table: {e}"))?;         let head = root.head().ok_or_else(|| "Cannot parse lib table: no root expression found".to_string())?;@@ -939,7 +977,7 @@ pub fn list_footprints(ctx: &LibCtx, want: Option<&str>) -> (Vec<(String, Pretty     };     if let Some(table) = ctx.fp_lib_table() {         if table.is_file() {-            if let Ok(t) = LibTable::parse_file(&table) {+            if let Ok(t) = LibTable::parse_file_resolved(&table) {                 for e in t.entries.iter().filter(|e| !e.disabled) {                     if let Some(resolved) = expand_kicad_uri(&e.uri, ctx) {                         add(&e.name, Path::new(&resolved), &mut libs);@@ -1054,6 +1092,23 @@ mod tests {         assert!(t.to_text().ends_with("(descr \"Adom Bridge - custom footprints\"))\n)\n"));     } +    #[test]+    fn a_fresh_kicad10_nested_table_row_is_followed_for_reads_only() {+        let d = std::env::temp_dir().join(format!("kicad-core-nested-{}", std::process::id()));+        let _ = std::fs::remove_dir_all(&d);+        std::fs::create_dir_all(&d).unwrap();+        let stock = d.join("stock-sym-lib-table");+        std::fs::write(&stock, "(sym_lib_table\n  (version 7)\n  (lib (name \"Device\")(type \"KiCad\")(uri \"${KICAD10_SYMBOL_DIR}/Device.kicad_sym\")(options \"\")(descr \"\"))\n  (lib (name \"Adom\")(type \"KiCad\")(uri \"stock-adom\")(options \"\")(descr \"\"))\n)\n").unwrap();+        let user = d.join("sym-lib-table");+        std::fs::write(&user, format!("(sym_lib_table\n  (version 7)\n  (lib (name \"KiCad\")(type \"Table\")(uri \"{}\")(options \"\")(descr \"KiCad Default Libraries\"))\n  (lib (name \"Adom\")(type \"KiCad\")(uri \"user-adom\")(options \"\")(descr \"\"))\n)\n", norm_path(&stock))).unwrap();+        let plain = LibTable::parse_file(&user).unwrap();+        assert!(!plain.has_library("Device") && plain.has_library("KiCad")); // the writers' view is untouched+        let r = LibTable::parse_file_resolved(&user).unwrap();+        assert!(r.has_library("Device") && !r.has_library("KiCad"));+        assert_eq!(r.get("Adom").unwrap().uri, "user-adom"); // the outer row wins+        let _ = std::fs::remove_dir_all(&d);+    }+     #[test]     fn missing_table_file_is_empty() {         let t = LibTable::parse_file(Path::new("/nonexistent/dir/sym-lib-table")).unwrap();
rust/crates/kicad-core/src/schematic.rs+1−1
@@ -1014,7 +1014,7 @@ pub fn resolve_lib_symbol(ctx: &LibCtx, lib_id: &str, project_dir: Option<&Path>             continue;         }         checked.push(libraries::norm_path(t));-        let table = LibTable::parse_file(t)?;+        let table = LibTable::parse_file_resolved(t)?;         let Some(entry) = table.get(nick) else { continue };         let uri = match project_dir {             Some(d) => entry.uri.replace("${KIPRJMOD}", &libraries::norm_path(d)),