← Commit history

1.0.33: NATIVE (Rust) build. 1.0.33 (wiki #109): model check and 3D show verbs report the 3D viewer's model-visibility toggles and say when a hidden class exists on the board; new kicad_3d_viewer_settings; failed kicad_close lists dialogsSeen; show verbs wait for the file's title before capturing.

John Lauer ·7ed92cf7c7 ·1mo ago ·parent c11b683
5 files changed +67−9
rust/Cargo.lock+3−3
@@ -244,7 +244,7 @@ dependencies = [  [[package]] name = "kicad-bridge"-version = "1.0.32"+version = "1.0.33" dependencies = [  "kicad-core",  "kicad-platform",@@ -255,7 +255,7 @@ dependencies = [  [[package]] name = "kicad-core"-version = "1.0.32"+version = "1.0.33" dependencies = [  "kicad-ipc-rs",  "kicad-platform",@@ -281,7 +281,7 @@ dependencies = [  [[package]] name = "kicad-platform"-version = "1.0.32"+version = "1.0.33" dependencies = [  "serde",  "serde_json",
rust/Cargo.toml+1−1
@@ -3,7 +3,7 @@ resolver = "2" members = ["crates/*"]  [workspace.package]-version = "1.0.32"+version = "1.0.33" edition = "2021" license = "MIT" publish = false
rust/crates/kicad-bridge/src/verbs_maint.rs+22
@@ -14,6 +14,16 @@ use kicad_core::{install_kicad, pcm, updates}; use kicad_platform::native;  pub static VERBS: &[Verb] = &[+    Verb {+        name: "kicad_3d_viewer_settings",+        summary: "Read or set the 3D viewer's model-visibility toggles (SMD, through-hole, virtual, not-in-POS, DNP) that can hide every chip body.",+        mechanism: Mechanism::File, risk: "write", timeout_sec: 30,+        input: "{\"smd\"?: bool, \"throughHole\"?: bool, \"virtual\"?: bool, \"notInPosFile\"?: bool, \"dnp\"?: bool}",+        example: "kicad_3d_viewer_settings {\"smd\": true}",+        hint: "No args reads <config>/3d_viewer.json render.show_footprints_*. These toggles hide whole classes of footprint bodies in the 3D viewer AND in kicad-cli pcb render, so a board whose every model resolves can still show pads only (insert is KiCad's word for SMD, normal for through-hole). Setting one refuses while KiCad runs, because KiCad rewrites the file from memory when it exits: kicad_close, set, then kicad_show_3d_board again. Other keys are preserved and the previous file is kept as 3d_viewer.json.adom.bak.",+        related: &["kicad_model_check", "kicad_show_3d_board", "kicad_show_3d_chip", "kicad_close"],+        pitfalls: &["kicad_model_check reports the same flags with a _viewerHint when a hidden class exists on the board: read that before you bisect a board.", "These are the USER's viewer preferences: change them because they asked or because the proof needs it, and say that you did."],+    },     Verb {         name: "kicad_taskbar_repair",         summary: "Fix the white taskbar icon on standalone KiCad editors by writing KiCad's own AUMID onto its Start Menu shortcuts.",@@ -129,6 +139,18 @@ pub static VERBS: &[Verb] = &[ pub fn dispatch(state: &mut State, command: &str, args: &Value) -> Option<Value> {     let out = match command {         "kicad_taskbar_repair" => taskbar_repair(&state.kicad_info().version),+        "kicad_3d_viewer_settings" => {+            let ctx = Ctx::from_info(&state.kicad_info());+            let sets = model_check::VIEWER_FLAGS.iter().any(|(k, _, _)| args.get(*k).map(|v| !v.is_null()).unwrap_or(false));+            let (running, _, _, pids) = install_kicad::running_report();+            if sets && running == json!(true) {+                json!({"success": false, "errorCode": "kicad_running", "mutated": false, "kicadPids": pids, "viewer": model_check::viewer_visibility(&ctx),+                    "error": "KiCad is running and rewrites 3d_viewer.json from memory when it exits, which would undo this edit.",+                    "_hint": "kicad_close (it refuses on unsaved work rather than lose it), call this verb again, then reopen the board and kicad_show_3d_board."})+            } else {+                model_check::set_viewer_visibility(&ctx, args)+            }+        }         "kicad_model_check" => {             let ctx = Ctx::from_info(&state.kicad_info());             model_check::handle(&ctx, args)
rust/crates/kicad-bridge/src/verbs_show.rs+33−3
@@ -436,6 +436,23 @@ fn verify_window(mut result: Value, kind: WindowKind, label: &str, needle: &str,     }     step(command, &format!("verifying the {label} window"), 70);     let prefer = result.get("hwnd").and_then(Value::as_u64).filter(|h| *h != 0);+    // Wiki #109 ask 4: the window exists before the file has loaded ("untitled [Unsaved]"),+    // and a capture taken then is an empty sheet. Wait for the title to name the file.+    if let (Some(h), false) = (prefer, needle.is_empty()) {+        let mut confirmed = false;+        for _ in 0..24 {+            win::invalidate();+            if win::kicad_windows().iter().any(|w| w.hwnd == h && w.title.to_lowercase().contains(needle)) {+                confirmed = true;+                break;+            }+            sleep_ms(500);+        }+        result["titleConfirmed"] = json!(confirmed);+        if !confirmed {+            result["_titleHint"] = json!("The window never showed the requested file's name in its title within 12 s, so the capture may be of a sheet that had not loaded. Call the verb again; the editor is warm now.");+        }+    }     if let Some(w) = wait_for_surface(kind, prefer, needle, 12, 500) {         let slug = label.to_lowercase().replace(' ', "-");         with_evidence(&mut result, &w, &slug, args, command);@@ -595,10 +612,16 @@ fn show_3d_chip(state: &mut State, args: &Value) -> Value {         let mc = model_check::check_footprint(&ctx, &lib_t, &fp_t, None);         if is_true(&mc, "success") {             let warnings = mc["warnings"].as_array().cloned().unwrap_or_default();+            let viewer = model_check::viewer_visibility(&ctx);             r["models"] = json!({                 "ok": mc["ok"], "count": mc["models"].as_array().map(|a| a.len()).unwrap_or(0),-                "warnings": warnings, "renderVerified": false, "note": mc["note"],+                "warnings": warnings, "renderVerified": false, "note": mc["note"], "viewer": viewer,             });+            // Wiki #109: a hidden class in the viewer's own settings shows a chip as pads only.+            let off: Vec<&str> = model_check::VIEWER_FLAGS.iter().filter(|(k, _, _)| *k != "dnp" && viewer["flags"][*k] == json!(false)).map(|(_, _, label)| *label).collect();+            if !off.is_empty() {+                r["_modelHint"] = json!(format!("The 3D viewer settings hide {}: a chip of that class shows pads only even when its model resolves. kicad_3d_viewer_settings shows and sets these toggles.", off.join(", ")));+            }             if let Some(ws) = mc["warnings"].as_array().filter(|w| !w.is_empty()) {                 let parts: Vec<String> = ws.iter().take(3).map(|w| format!("{}: {}", w["code"].as_str().unwrap_or("?"), w["hint"].as_str().unwrap_or(""))).collect();                 r["_modelHint"] = json!(format!("The 3D view may show pads only: {} (kicad_model_check has the full list).", parts.join("; ")));@@ -670,10 +693,17 @@ fn show_3d_board(state: &mut State, args: &Value) -> Value {         let ctx = Ctx::from_info(&state.kicad_info());         let mc = model_check::check_board(&ctx, f, 20);         if is_true(&mc, "success") {-            r["models"] = json!({"ok": mc["ok"], "counts": mc["counts"], "renderVerified": false, "footprintsWithWarnings": mc["footprintsWithWarnings"]});+            r["models"] = json!({"ok": mc["ok"], "counts": mc["counts"], "renderVerified": false, "footprintsWithWarnings": mc["footprintsWithWarnings"],+                "viewer": mc["viewer"], "footprintsWithModelByViewerClass": mc["footprintsWithModelByViewerClass"]});+            // Wiki #109: every model file can resolve and the viewer still shows pads only,+            // because its own toggles hide a whole class of bodies. Say so here.+            if let Some(h) = mc["_viewerHint"].as_str() {+                r["_modelHint"] = json!(h);+            }             if !is_true(&mc, "ok") {                 let c = &mc["counts"];-                r["_modelHint"] = json!(format!("{} footprint(s) have no model and {} reference a file KiCad cannot find; those render as pads only. kicad_model_check {{boardPath}} lists them.", c["missingModel"], c["unresolvedModel"]));+                let viewer_part = mc["_viewerHint"].as_str().map(|h| format!(" Also: {h}")).unwrap_or_default();+                r["_modelHint"] = json!(format!("{} footprint(s) have no model and {} reference a file KiCad cannot find; those render as pads only. kicad_model_check {{boardPath}} lists them.{viewer_part}", c["missingModel"], c["unresolvedModel"]));             }         }     }
rust/crates/kicad-bridge/src/verbs_windows.rs+8−2
@@ -2469,11 +2469,17 @@ fn close_kicad(_state: &mut State, args: &Value) -> Value {     if !failed.is_empty() {         // No save prompt and no close: the native build never kills here (force:true is         // the only kill path); say what did not close and how to force it.+        // Wiki #109 ask 3: a dirty Symbol Editor refused to go and the reply named no cause.+        // Report every dialog that is up (the unrecognised save prompt is among them) and+        // the editor's own close verb.+        let seen: Vec<Value> = scan_dialogs().iter().map(|d| json!({"hwnd": d.hwnd, "title": d.title, "body": d.body.chars().take(240).collect::<String>()})).collect();+        let editor_verbs: Vec<&str> = [("symbol editor", "kicad_close_symbol_editor"), ("footprint editor", "kicad_close_footprint_editor"), ("3d viewer", "kicad_close_3d_viewer")]+            .iter().filter(|(word, _)| failed.iter().any(|t| t.to_lowercase().contains(word))).map(|(_, verb)| *verb).collect();         return json!({-            "success": false, "errorCode": "close_failed",+            "success": false, "errorCode": "close_failed", "dialogsSeen": seen, "editorCloseVerbs": editor_verbs,             "error": format!("Failed to close: {}", failed.join(", ")),             "output": if closed.is_empty() { String::new() } else { format!("Closed: {}", closed.join(", ")) },-            "_hint": "Call close_kicad with {\"force\": true} to force-kill stuck windows (a kill discards unsaved work silently). kicad_screenshot_all shows whether a dialog is holding the window.",+            "_hint": "A title starting with * has unsaved work: its editor is waiting on a prompt. dialogsSeen lists every dialog that is up right now; answer it (kicad_dismiss_dialogs, or discardChanges:true to press Discard) or close that editor with the verb in editorCloseVerbs, then call kicad_close again. force:true kills the process and discards unsaved work silently. kicad_screenshot_all shows the windows.",         });     }     let mut out = json!({"success": true, "output": format!("KiCad closed ({} window{}).", closed.len(), if closed.len() != 1 { "s" } else { "" }), "data": {}});