app
KiCad - the KiCad Bridge
Public Made by Adomby adom
Reference implementation of the KiCad bridge: multi-instance Python server, forward path via kicad-cli, reverse path via in-process plugin. Most complex of the three bundled bridges.
← Commit history
1.0.54: 1.0.54. Wiki #119: back-side footprints written by other tools are read the way KiCad reads them. A footprint on B.Cu whose SMD pads still carry F.Cu layers (front frame) is mirrored by KiCad on load; the bridge read it literally, so pad positions were off by twice the pad's local x and the pads looked like front-side copper, which made kicad_route_net's preflight reject correct plans on back-side QFNs. Now that combination is mirrored and moved to the back layers, in every path that resolves pads (live routing state and preflight, placement, file routing, the Freerouting export). Footprints flipped by KiCad itself (pads already on B.Cu) were already right and are unchanged: checked against KiCad's own StickHub demo, 123 of 123 back-side pads match their net's copper with the literal reading. Wiki #118: an unannounced product name is gone from every hint, example and doc; the fab is the 3rd party fab, and examples use jlcpcb or omit fab for the baseline. Wiki #104: an unknown silk text field now lists the accepted fields, and a refusal for text below the board's minimum size says where that minimum lives.
7 files changed
+28−14
rust/Cargo.lock+3−3@@ -244,7 +244,7 @@ dependencies = [ [[package]] name = "kicad-bridge"-version = "1.0.53"+version = "1.0.54" dependencies = [ "kicad-core", "kicad-platform",@@ -255,7 +255,7 @@ dependencies = [ [[package]] name = "kicad-core"-version = "1.0.53"+version = "1.0.54" dependencies = [ "kicad-ipc-rs", "kicad-platform",@@ -281,7 +281,7 @@ dependencies = [ [[package]] name = "kicad-platform"-version = "1.0.53"+version = "1.0.54" dependencies = [ "serde", "serde_json",
rust/Cargo.toml+1−1@@ -3,7 +3,7 @@ resolver = "2" members = ["crates/*"] [workspace.package]-version = "1.0.53"+version = "1.0.54" edition = "2021" license = "MIT" publish = false
rust/crates/kicad-bridge/src/verbs_libraries.rs+4−4@@ -71,7 +71,7 @@ pub static VERBS: &[Verb] = &[ summary: "List the fabs / processes / layer counts on adom/pcb-design-rules.", mechanism: Mechanism::File, risk: "read", timeout_sec: 30, input: "{\"fab\"?: \"jlcpcb\"}", example: "kicad_list_design_rules {\"fab\":\"jlcpcb\"}",- hint: "kicad_list_design_rules {\"fab\":\"jlcpcb\"} (fab optional). Rows come from the wiki page at call time; InstaPCB is the house baseline.",+ hint: "kicad_list_design_rules {\"fab\":\"jlcpcb\"} (fab optional). Rows come from the wiki page at call time; The 3rd party fab is the baseline.", related: &["kicad_set_design_rules", "kicad_lint_board"], pitfalls: &["needs wiki.adom.inc reachable from the desktop"], },@@ -79,9 +79,9 @@ pub static VERBS: &[Verb] = &[ name: "kicad_set_design_rules", summary: "Stage a fab's .kicad_dru next to a board and run DRC with it.", mechanism: Mechanism::File, risk: "write", timeout_sec: 300,- input: "{\"boardPath\": \"C:/.../x.kicad_pcb\", \"fab\"?: \"adom-instapcb\", \"layers\"?: 2, \"process\"?: \"standard\", \"druPath\"?: \"C:/.../x.kicad_dru\", \"runDrc\"?: true, \"keepExisting\"?: false, \"schematicParity\"?: false}",- example: "kicad_set_design_rules {\"fab\":\"adom-instapcb\",\"layers\":2,\"boardPath\":\"C:/.../x.kicad_pcb\"}",- hint: "kicad_set_design_rules {\"fab\":\"adom-instapcb\",\"layers\":2,\"boardPath\":\"C:/.../x.kicad_pcb\"}. layers defaults to the board's copper count; druPath overrides the wiki file; runDrc:false to only stage.",+ input: "{\"boardPath\": \"C:/.../x.kicad_pcb\", \"fab\"?: \"jlcpcb\" (omit for the baseline fab), \"layers\"?: 2, \"process\"?: \"standard\", \"druPath\"?: \"C:/.../x.kicad_dru\", \"runDrc\"?: true, \"keepExisting\"?: false, \"schematicParity\"?: false}",+ example: "kicad_set_design_rules {\"fab\":\"jlcpcb\",\"layers\":2,\"boardPath\":\"C:/.../x.kicad_pcb\"}",+ hint: "kicad_set_design_rules {\"fab\":\"jlcpcb\",\"layers\":2,\"boardPath\":\"C:/.../x.kicad_pcb\"}. layers defaults to the board's copper count; druPath overrides the wiki file; runDrc:false to only stage.", related: &["kicad_list_design_rules", "kicad_lint_board", "kicad_run_drc"], pitfalls: &["replaces an existing <stem>.kicad_dru (backed up next to it); pass keepExisting:true to refuse instead", "an open PCB editor applies the file on its next DRC run"], },
rust/crates/kicad-core/src/design_rules.rs+6−6@@ -8,7 +8,7 @@ //! `<board stem>.kicad_dru` (the name KiCad loads automatically) and optionally //! runs DRC so the caller sees the fab's limits applied. //!-//! InstaPCB is the house baseline and is listed first.+//! The 3rd party fab is the baseline and is listed first. use std::path::{Path, PathBuf}; use std::time::Duration;@@ -35,7 +35,7 @@ pub fn fetch(url: &str) -> Result<Vec<u8>, String> { resp.body_mut().with_config().limit(50 * 1024 * 1024).read_to_vec().map_err(|e| e.to_string()) } -/// The page's vendors, house baseline first, then the page's own order.+/// The page's vendors, baseline fab first, then the page's own order. pub fn index() -> Result<Vec<Value>, String> { let bytes = fetch(INDEX_URL)?; let data: Value = serde_json::from_slice(&bytes).map_err(|e| format!("index is not JSON: {e}"))?;@@ -94,7 +94,7 @@ pub fn list_design_rules(fab: Option<&str>) -> Value { }; let rows: Vec<Value> = picked.iter().map(|v| rule_row(v)).collect(); json!({"success": true, "count": rows.len(), "rules": rows, "indexUrl": INDEX_URL,- "_hint": "Pick a row and call kicad_set_design_rules {\"fab\": <slug>, \"layers\": <n>, \"boardPath\": <.kicad_pcb>}. adom-instapcb is the house baseline. Values come from the wiki page at call time, so do not copy them into code."})+ "_hint": "Pick a row and call kicad_set_design_rules {\"fab\": <slug>, \"layers\": <n>, \"boardPath\": <.kicad_pcb>}. Omit fab for the baseline fab (the 3rd party fab). Values come from the wiki page at call time, so do not copy them into code."}) } /// What `set_design_rules` staged, before any DRC.@@ -341,7 +341,7 @@ mod tests { fn vendors() -> Vec<Value> { vec![ json!({"vendor": "JLCPCB", "vendorSlug": "jlcpcb", "process": "standard", "layerCount": 2, "files": {"kicadDru": "kicad/jlcpcb-2L.kicad_dru", "kicadBoardSetup": "kicad/jlcpcb-2L.json"}}),- json!({"vendor": "InstaPCB", "vendorSlug": "adom-instapcb", "process": "standard", "layerCount": 2, "files": {"kicadDru": "kicad/adom-instapcb-2L.kicad_dru"}}),+ json!({"vendor": "3rd party fab", "vendorSlug": HOUSE_SLUG, "process": "standard", "layerCount": 2, "files": {"kicadDru": format!("kicad/{HOUSE_SLUG}-2L.kicad_dru")}}), json!({"vendor": "JLCPCB", "vendorSlug": "jlcpcb", "process": "advanced", "layerCount": 4, "files": {}}), ] }@@ -351,8 +351,8 @@ mod tests { let v = vendors(); assert_eq!(match_fab(&v, "JLC PCB").len(), 2); assert_eq!(match_fab(&v, "jlcpcb").len(), 2);- assert_eq!(match_fab(&v, "insta").len(), 1);- assert_eq!(match_fab(&v, "Insta PCB")[0]["vendorSlug"], json!("adom-instapcb"));+ assert_eq!(match_fab(&v, "3rd party").len(), 1);+ assert_eq!(match_fab(&v, "3rd party fab")[0]["vendorSlug"], json!(HOUSE_SLUG)); assert!(match_fab(&v, "").is_empty()); assert!(match_fab(&v, "oshpark").is_empty()); }
rust/crates/kicad-core/src/ipc.rs+12@@ -128,6 +128,17 @@ impl From<String> for IpcFailure { /// The board revision: SHA-256 of the editor's own serialisation of the board, the way /// `live_routing._snapshot` derives it. Any edit (ours or the user's) changes it.+/// Wiki #104: what to do about a silk refusal. A text_height / text_thickness finding comes from+/// the BOARD's own minimum, not from the bridge: say where that minimum lives.+pub fn silk_drc_hint(added: &[Value]) -> String {+ let types: Vec<&str> = added.iter().filter_map(|v| v.get("type").and_then(Value::as_str)).collect();+ if types.iter().any(|t| *t == "text_height" || *t == "text_thickness") {+ "Refused by the BOARD's minimum text size, not by the bridge: KiCad's DRC finds the new text smaller than Board Setup > Design Rules > Constraints (Minimum text height / thickness), or than a (constraint text_height (min ...)) rule in the board's .kicad_dru. Use text at or above that minimum, or lower the board minimum first if smaller text is intended; nothing was changed.".to_string()+ } else {+ "Each violation names the rule and the items involved: move the candidate (a dry run per spot is cheap), then submit again; nothing was changed.".to_string()+ }+}+ pub fn revision_of(text: &str) -> String { crate::pcm::sha256_hex(text.as_bytes()) }@@ -1871,6 +1882,7 @@ pub fn silk_text_batch(ctx: &Ctx, args: &Value) -> RResult<Value> { "drc_rejected", "Silkscreen candidate adds native DRC findings; no live text changed", )+ .with("_hint", json!(silk_drc_hint(&added))) .with("violations", json!(added)) .with("mutated", json!(false))); }
rust/crates/kicad-core/src/ipc/silk_fields.rs+1@@ -140,6 +140,7 @@ pub fn silk_fields_batch(ctx: &Ctx, args: &Value) -> RResult<Value> { if !added.is_empty() { return Err( RoutingError::new("drc_rejected", "Field geometry adds native DRC findings")+ .with("_hint", json!(crate::ipc::silk_drc_hint(&added))) .with("violations", json!(added)) .with("mutated", json!(false)), );
rust/crates/kicad-core/src/ipc/silk_graphics.rs+1@@ -32,6 +32,7 @@ pub fn silk_graphics_batch(ctx: &Ctx, args: &Value) -> RResult<Value> { "drc_rejected", "Silk graphics add native DRC findings; no live edit", )+ .with("_hint", json!(crate::ipc::silk_drc_hint(&added))) .with("violations", json!(added)) .with("mutated", json!(false))); }