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.11: 1.0.11: kicad_menu_items lists a window's menu bar (path, id, accelerator), so menu commands are chosen from what the build actually has; exact-label matching from 1.0.10
3 files changed
+33−4
rust/Cargo.lock+3−3@@ -244,7 +244,7 @@ dependencies = [ [[package]] name = "kicad-bridge"-version = "1.0.10"+version = "1.0.11" dependencies = [ "kicad-core", "kicad-platform",@@ -255,7 +255,7 @@ dependencies = [ [[package]] name = "kicad-core"-version = "1.0.10"+version = "1.0.11" dependencies = [ "kicad-ipc-rs", "kicad-platform",@@ -281,7 +281,7 @@ dependencies = [ [[package]] name = "kicad-platform"-version = "1.0.10"+version = "1.0.11" dependencies = [ "serde", "serde_json",
rust/Cargo.toml+1−1@@ -3,7 +3,7 @@ resolver = "2" members = ["crates/*"] [workspace.package]-version = "1.0.10"+version = "1.0.11" edition = "2021" license = "MIT" publish = false
rust/crates/kicad-bridge/src/verbs_windows.rs+29@@ -96,6 +96,16 @@ pub static VERBS: &[Verb] = &[ related: &["kicad_install_footprint", "kicad_close_footprint_editor", "kicad_screenshot_all"], pitfalls: &["the footprint must be in an installed fp-lib-table library (install_footprint first)", "allowCursorClick (the SendInput launcher click) is not in the native build: the background menu and UIA rungs are the only ones"], },+ Verb {+ name: "kicad_menu_items",+ summary: "List a KiCad window's menu bar items (path, id, accelerator), optionally filtered by a substring: what kicad_send_key {menu} and kicad_3d_view can fire.",+ mechanism: Mechanism::Window, risk: "read", timeout_sec: 30,+ input: "{\"hwnd\"?: 123, \"contains\"?: \"zoom\"}",+ example: "kicad_menu_items {\"hwnd\":123456,\"contains\":\"zoom\"}",+ hint: "Read-only, background. The last path element is the label a menu command matches (exact label first, then substring).",+ related: &["kicad_send_key", "kicad_3d_view", "kicad_window_info"],+ pitfalls: &["menus differ between KiCad versions and editors; list before you assume a label"],+ }, Verb { name: "kicad_3d_view", summary: "Drive the 3D Viewer without the mouse: zoom, rotate, view orientation, flip, or a scripted tour; menu commands posted to the viewer in the background.",@@ -292,6 +302,7 @@ pub fn dispatch(state: &mut State, command: &str, args: &Value) -> Option<Value> "kicad_open_footprint_editor" => open_footprint_editor(state, args), "kicad_open_3d_viewer" => open_3d_viewer(args), "kicad_3d_view" => view_3d(args),+ "kicad_menu_items" => menu_items(args), "kicad_open_editors" => open_editors(), "kicad_close" => close_kicad(state, args), "kicad_close_symbol_editor" => close_kind(WindowKind::SymbolEditor, "Symbol Editor"),@@ -1875,6 +1886,24 @@ fn open_3d_from(frame: u64, label: &str, pathways: &mut Vec<String>) -> Result<O /// `kicad_3d_view`: the 3D Viewer's View-menu commands by name, repeated with pauses, or a tour.++/// `kicad_menu_items`: the native menu bar of a KiCad window, read-only.+pub(crate) fn menu_items(args: &Value) -> Value {+ let hwnd = match arg_any_u64(args, &["hwnd"]).or_else(|| foreground_kicad_hwnd()) {+ Some(h) => h,+ None => return fail("no_window", "No KiCad window found.", "kicad_window_info lists the windows; pass hwnd."),+ };+ let needle = arg_str(args, "contains").map(|s| s.to_lowercase()).unwrap_or_default();+ match native().menu_tree(hwnd) {+ Ok(items) => {+ let list: Vec<Value> = items.iter().filter(|it| needle.is_empty() || it.path.iter().any(|p| p.replace('&', "").to_lowercase().contains(&needle))).map(|it| json!({"path": it.path.iter().map(|p| p.replace('&', "")).collect::<Vec<_>>(), "id": it.id, "accel": it.accel})).collect();+ json!({"success": true, "hwnd": hwnd, "title": title_of(hwnd), "count": list.len(), "items": list})+ }+ Err(e) if is_not_impl(&e) => unsupported("native menu walk (menu_tree)"),+ Err(e) => fail("menu_walk_failed", &e, "Retry once the window is idle."),+ }+}+ pub(crate) fn view_3d(args: &Value) -> Value { const ACTIONS: &[(&str, &str)] = &[ ("zoom_in", "Zoom In"), ("zoom_out", "Zoom Out"), ("fit", "Zoom to Fit"),