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
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.
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": {}});