app
KiCad - the KiCad Bridge
Public Made by Adomby adom
Drive your own KiCad from an AI, in the background: open every editor, read the whole board as data (the EDA Bridge Standard board model), stack-up, part heights, netlists, place, route, pour, DRC and exports.
← Commit history
Merge PR #417: 1.0.102: no paint wait or blank warning for an empty editor
3 files changed
+45−6
rust/Cargo.lock+3−3@@ -319,7 +319,7 @@ dependencies = [ [[package]] name = "kicad-bridge"-version = "1.0.101"+version = "1.0.102" dependencies = [ "adom-eda-checks", "kicad-core",@@ -331,7 +331,7 @@ dependencies = [ [[package]] name = "kicad-core"-version = "1.0.101"+version = "1.0.102" dependencies = [ "kicad-ipc-rs", "kicad-platform",@@ -358,7 +358,7 @@ dependencies = [ [[package]] name = "kicad-platform"-version = "1.0.101"+version = "1.0.102" dependencies = [ "serde", "serde_json",
rust/Cargo.toml+1−1@@ -3,7 +3,7 @@ resolver = "2" members = ["crates/*"] [workspace.package]-version = "1.0.101"+version = "1.0.102" edition = "2021" license = "MIT" publish = false
rust/crates/kicad-bridge/src/verbs_show.rs+41−2@@ -285,6 +285,28 @@ fn editor_showing(kind: WindowKind, confirms: impl Fn(&str) -> bool) -> Option<w // ── Evidence and the shot ───────────────────────────────────────────────────── +/// An editor with no part loaded ("[no symbol loaded] — Symbol Editor"): its canvas has only the+/// grid to show, so a blank one is the right answer and there is nothing to wait for.+fn editor_is_empty(title: &str) -> bool {+ let t = title.trim_start_matches('*').trim_start();+ t.starts_with("[no ") && t.contains(" loaded]")+}++/// How many captures to try for a canvas to paint.+fn paint_attempts(title: &str, attempts: usize) -> usize {+ if editor_is_empty(title) { 1 } else { attempts }+}++/// A blank canvas in an empty editor is not a fault: say that instead of warning about it.+fn note_empty_canvas(out: &mut Value, title: &str) {+ if editor_is_empty(title) {+ if let Some(o) = out.as_object_mut() {+ o.remove("_canvasHint");+ }+ out["canvasExpectedEmpty"] = json!(true);+ }+}+ /// Capture a shot of `hwnd` through ab, polling until the GL canvas has painted (0.9.202: /// poll rather than assume; each attempt is a real capture). Returns (shot, attempts waited). fn capture_settled(hwnd: u64, label: &str, resize_max: Option<u32>, max_attempts: usize) -> Result<(Value, usize), String> {@@ -319,7 +341,7 @@ fn canvas_fields(out: &mut Value, shot: &Value, waited: usize, hint_when_blank: /// The `evidence` block (Python `_capture_evidence`): PROOF the surface is really on screen, /// captured in the background from the verified hwnd. fn capture_evidence(w: &win::Win, label: &str) -> Result<Value, String> {- let (shot, waited) = capture_settled(w.hwnd, label, None, 8)?;+ let (shot, waited) = capture_settled(w.hwnd, label, None, paint_attempts(&w.title, 8))?; let path = shot.get("fullPath").or_else(|| shot.get("savedTo")).cloned().unwrap_or(Value::Null); let mut ev = json!({ "window": w.title,@@ -337,6 +359,7 @@ fn capture_evidence(w: &win::Win, label: &str) -> Result<Value, String> { } canvas_fields(&mut ev, &shot, waited, "The window is correct but its drawing canvas is still BLANK ({colors} distinct colour(s)) after waiting {waited}s for it to paint. Usually this just means KiCad is still loading libraries - check the status bar in the image. If it never paints, the canvas is an OpenGL surface and yields no picture on a host with no usable GPU or no active console session; kicad_enable_software_opengl is the last-resort fix there. Either way, treat this thumbnail as proof of the WINDOW, not of the part.");+ note_empty_canvas(&mut ev, &w.title); Ok(ev) } @@ -365,7 +388,7 @@ fn build_shot(w: &win::Win, label: &str, max_width: Option<&Value>) -> Result<Va let mw = clamp_max_width(max_width); // Wiki #52: the editor answers "ready" before its GL canvas first paints, and blanks // cluster on FAST parts. Own the settle here too (up to 10 s) and REPORT the verdict.- let (shot, waited) = capture_settled(w.hwnd, &format!("shot-{label}"), Some(mw), 11)?;+ let (shot, waited) = capture_settled(w.hwnd, &format!("shot-{label}"), Some(mw), paint_attempts(&w.title, 11))?; let full = shot.get("fullPath").or_else(|| shot.get("savedTo")).and_then(Value::as_str).unwrap_or("").to_string(); let safe = shot.get("safePath").and_then(Value::as_str).unwrap_or("").to_string(); let resized = !safe.is_empty() && std::path::Path::new(&safe).is_file();@@ -401,6 +424,7 @@ fn build_shot(w: &win::Win, label: &str, max_width: Option<&Value>) -> Result<Va out["thumbnailUnavailable"] = json!(format!("ab returned no resized (.safe) variant for this capture, so `image` is the FULL-resolution frame and no thumbnailDataUrl is offered (the native bridge does not decode or downscale images; maxWidth {mw} was requested from ab). Render `image` directly or pull `path`.")); } canvas_fields(&mut out, &shot, waited, "The canvas region is still UNRENDERED after {waited}s of polling - treat this frame as proof of the window, not the part, and recapture rather than render it.");+ note_empty_canvas(&mut out, &w.title); Ok(out) } @@ -873,6 +897,21 @@ fn verb_times(args: &Value) -> Value { #[cfg(test)] mod tests { use super::*;+ #[test]+ fn an_empty_editor_is_not_waited_on_or_warned_about() {+ assert_eq!(paint_attempts("[no symbol loaded] \u{2014} Symbol Editor", 11), 1);+ assert_eq!(paint_attempts("[no footprint loaded] \u{2014} Footprint Editor", 8), 1);+ assert_eq!(paint_attempts("Device:R [Read Only Library] \u{2014} Symbol Editor", 11), 11, "a loaded part still gets its wait");+ let mut v = json!({"canvasRendered": false, "_canvasHint": "still BLANK"});+ note_empty_canvas(&mut v, "[no symbol loaded] \u{2014} Symbol Editor");+ assert!(v.get("_canvasHint").is_none());+ assert_eq!(v["canvasExpectedEmpty"], true);+ let mut v = json!({"canvasRendered": false, "_canvasHint": "still BLANK"});+ note_empty_canvas(&mut v, "Device:R \u{2014} Symbol Editor");+ assert_eq!(v["_canvasHint"], "still BLANK", "a part that didn't paint is still a fault");+ assert!(v.get("canvasExpectedEmpty").is_none());+ }+ #[test] fn catalog_matches_bridge_json_and_carries_no_em_dash() {