← Commit history

Merge PR #417: 1.0.102: no paint wait or blank warning for an empty editor

Colby Knox ·7fe852af4d ·6d ago ·parent 1c9eb06
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() {