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.7: 1.0.7: kicad_remove_zone (zones by id, one Undo step); kicad_zone_state names zone types (copper, keepout, teardrop)
4 files changed
+61−4
rust/Cargo.lock+3−3@@ -244,7 +244,7 @@ dependencies = [ [[package]] name = "kicad-bridge"-version = "1.0.6"+version = "1.0.7" dependencies = [ "kicad-core", "kicad-platform",@@ -255,7 +255,7 @@ dependencies = [ [[package]] name = "kicad-core"-version = "1.0.6"+version = "1.0.7" dependencies = [ "kicad-ipc-rs", "kicad-platform",@@ -281,7 +281,7 @@ dependencies = [ [[package]] name = "kicad-platform"-version = "1.0.6"+version = "1.0.7" dependencies = [ "serde", "serde_json",
rust/Cargo.toml+1−1@@ -3,7 +3,7 @@ resolver = "2" members = ["crates/*"] [workspace.package]-version = "1.0.6"+version = "1.0.7" edition = "2021" license = "MIT" publish = false
rust/crates/kicad-bridge/src/verbs_routing.rs+13@@ -67,6 +67,16 @@ pub static VERBS: &[Verb] = &[ related: &["kicad_add_zone", "kicad_routing_validate"], pitfalls: &["Coverage is against the outline's bounding box; a non-rectangular board reads low.", "Pads, tracks and vias outside zones are not counted."], },+ Verb {+ name: "kicad_remove_zone",+ summary: "Remove zones (pours or keepouts) by id live as one Undo step.",+ mechanism: Mechanism::Ipc, risk: "write", timeout_sec: 130,+ input: "{\"filePath\", \"expectedRevision\", \"itemIds\": [\"<zone uuid>\", ...]}",+ example: "kicad_remove_zone {\"filePath\":\"C:/p/b.kicad_pcb\",\"expectedRevision\":\"<rev>\",\"itemIds\":[\"7c1e...\"]}",+ hint: "Ids from kicad_add_zone or kicad_zone_state. Refuses ids that are not zones; nothing removed then.",+ related: &["kicad_add_zone", "kicad_zone_state"],+ pitfalls: &["Does not save the file.", "KiCad's own teardrop zones are zones too; remove only what you added."],+ }, Verb { name: "kicad_routing_validate", summary: "Run KiCad DRC against the current unsaved live board snapshot.",@@ -147,6 +157,7 @@ pub fn dispatch(state: &mut State, command: &str, args: &Value) -> Option<Value> "kicad_routing_validate" => live(state, args, Live::Validate), "kicad_add_zone" => live(state, args, Live::AddZone), "kicad_zone_state" => live(state, args, Live::ZoneState),+ "kicad_remove_zone" => live(state, args, Live::RemoveZone), "kicad_board_pads" => board_pads(args), "kicad_add_track" => add_track(args), "kicad_add_via" => add_via(args),@@ -169,6 +180,7 @@ enum Live { Validate, AddZone, ZoneState,+ RemoveZone, } #[cfg(feature = "ipc")]@@ -186,6 +198,7 @@ fn live(state: &mut State, args: &Value, which: Live) -> Value { Live::Validate => ipc::routing_validate(&ctx, args), Live::AddZone => ipc::add_zone(&ctx, args), Live::ZoneState => ipc::zone_state(&ctx, args),+ Live::RemoveZone => ipc::remove_zone(&ctx, args), }; match result { Ok(v) => v,
rust/crates/kicad-core/src/ipc.rs+44@@ -852,6 +852,50 @@ pub fn add_zone(ctx: &Ctx, args: &Value) -> RResult<Value> { Ok(result) } +/// `kicad_remove_zone`: delete zones (copper pours or keepouts) by id as one Undo step. Only+/// zone items are accepted, so a pad, track or footprint id is refused with nothing removed.+pub fn remove_zone(ctx: &Ctx, args: &Value) -> RResult<Value> {+ let s = connect(ctx, args)?;+ check_revision(args, &s.revision()?)?;+ let ids: Vec<String> = match args.get("itemIds").or_else(|| args.get("zoneIds")) {+ Some(Value::Array(a)) if !a.is_empty() && a.len() <= 256 => a.iter().filter_map(Value::as_str).map(str::to_string).collect(),+ _ => return Err(RoutingError::new("invalid_item_ids", "Supply 1..256 zone itemIds returned by kicad_add_zone or kicad_zone_state")),+ };+ let raws = s.client.get_items_raw_by_type_codes(vec![PcbObjectTypeCode::new_zone().code]).map_err(map_err)?;+ let mut found = Vec::new();+ for a in &raws {+ if let Ok(z) = zones::decode_zone(&a.value) {+ if ids.contains(&z.id) {+ found.push(z);+ }+ }+ }+ if found.len() != ids.len() {+ let have: Vec<&str> = found.iter().map(|z| z.id.as_str()).collect();+ let missing: Vec<&String> = ids.iter().filter(|i| !have.contains(&i.as_str())).collect();+ return Err(RoutingError::new("invalid_item_ids", "Some IDs are not zones on the live board; nothing removed").with("missing", json!(missing)).with("mutated", json!(false)));+ }+ let count = ids.len();+ let label = format!("Adom remove {} zone(s)", count);+ let removed = transaction(&s, &label, || {+ let removed = s.client.delete_items(ids.clone())?;+ if removed.len() != count {+ return Err(IpcFailure::from(format!("KiCad removed {} of {} zones", removed.len(), count)));+ }+ Ok(removed)+ })?;+ let mut result = json!({+ "success": true, "mutated": true, "source": "live-editor", "removed": removed, "undoSteps": 1, "saved": false,+ "zones": found.iter().map(|z| json!({"id": z.id, "name": z.name, "net": z.net, "type": if z.rule_area { "keepout" } else { "copper" }})).collect::<Vec<_>>(),+ "_hint": "Zones removed as one KiCad Undo step. Other zones' fills may be stale: kicad_zone_state {refill:true} refills them.",+ });+ match s.revision() {+ Ok(r) => result["revision"] = json!(r),+ Err(e) => result["postCommitError"] = json!(e.message),+ }+ Ok(result)+}+ /// `kicad_zone_state`: every zone on the live board with its filled copper area per layer /// (KiCad's own fill polygons), the sum per layer and the coverage against the outline /// box. `refill:true` refills every zone first (the fill can be stale after routing).