From 3305de0a8e8d588494bed9fe27e0c1c298547307 Mon Sep 17 00:00:00 2001 From: Milind Sharma Date: Fri, 20 Feb 2026 18:11:11 +0800 Subject: [PATCH] feat(client): add RemoveFromSelection API and CLI command --- README.md | 6 +++--- docs/TEST_CLI.md | 6 ++++++ src/client.rs | 38 +++++++++++++++++++++++++++++++++++ test-scripts/kicad-ipc-cli.rs | 35 +++++++++++++++++++++++++++++++- 4 files changed, 81 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index b41cc65..66e48bd 100644 --- a/README.md +++ b/README.md @@ -50,11 +50,11 @@ Legend: | Section | Proto Commands | Implemented | Coverage | | --- | ---: | ---: | ---: | | Common (base) | 6 | 4 | 67% | -| Common editor/document | 23 | 14 | 61% | +| Common editor/document | 23 | 15 | 65% | | Project manager | 5 | 3 | 60% | | Board editor (PCB) | 22 | 20 | 91% | | Schematic editor (dedicated proto commands) | 0 | 0 | n/a | -| **Total** | **56** | **41** | **73%** | +| **Total** | **56** | **42** | **75%** | ### Common (base) @@ -87,7 +87,7 @@ Legend: | `GetBoundingBox` | Implemented | `KiCadClient::get_item_bounding_boxes` | | `GetSelection` | Implemented | `KiCadClient::get_selection_raw`, `KiCadClient::get_selection`, `KiCadClient::get_selection_summary`, `KiCadClient::get_selection_details` | | `AddToSelection` | Implemented | `KiCadClient::add_to_selection_raw`, `KiCadClient::add_to_selection` | -| `RemoveFromSelection` | Not yet | - | +| `RemoveFromSelection` | Implemented | `KiCadClient::remove_from_selection_raw`, `KiCadClient::remove_from_selection` | | `ClearSelection` | Implemented | `KiCadClient::clear_selection_raw`, `KiCadClient::clear_selection` | | `HitTest` | Implemented | `KiCadClient::hit_test_item` | | `GetTitleBlockInfo` | Implemented | `KiCadClient::get_title_block_info` | diff --git a/docs/TEST_CLI.md b/docs/TEST_CLI.md index 2d469e2..5521272 100644 --- a/docs/TEST_CLI.md +++ b/docs/TEST_CLI.md @@ -175,6 +175,12 @@ Add items to current selection: cargo run --bin kicad-ipc-cli -- add-to-selection --id --id ``` +Remove items from current selection: + +```bash +cargo run --bin kicad-ipc-cli -- remove-from-selection --id --id +``` + Clear current selection: ```bash diff --git a/src/client.rs b/src/client.rs index de9c263..a053950 100644 --- a/src/client.rs +++ b/src/client.rs @@ -69,6 +69,7 @@ const CMD_CHECK_PADSTACK_PRESENCE_ON_LAYERS: &str = const CMD_INJECT_DRC_ERROR: &str = "kiapi.board.commands.InjectDrcError"; const CMD_GET_SELECTION: &str = "kiapi.common.commands.GetSelection"; const CMD_ADD_TO_SELECTION: &str = "kiapi.common.commands.AddToSelection"; +const CMD_REMOVE_FROM_SELECTION: &str = "kiapi.common.commands.RemoveFromSelection"; const CMD_CLEAR_SELECTION: &str = "kiapi.common.commands.ClearSelection"; const CMD_BEGIN_COMMIT: &str = "kiapi.common.commands.BeginCommit"; const CMD_END_COMMIT: &str = "kiapi.common.commands.EndCommit"; @@ -795,6 +796,43 @@ impl KiCadClient { Ok(summarize_selection(items)) } + pub async fn remove_from_selection_raw( + &self, + item_ids: Vec, + ) -> Result, KiCadError> { + let command = common_commands::RemoveFromSelection { + header: Some(self.current_board_item_header().await?), + items: item_ids + .into_iter() + .map(|value| common_types::Kiid { value }) + .collect(), + }; + + let response = self + .send_command(envelope::pack_any(&command, CMD_REMOVE_FROM_SELECTION)) + .await?; + + match envelope::unpack_any::( + &response, + RES_SELECTION_RESPONSE, + ) { + Ok(payload) => Ok(payload.items), + Err(KiCadError::UnexpectedPayloadType { + expected_type_url: _, + actual_type_url, + }) if actual_type_url == envelope::type_url(RES_PROTOBUF_EMPTY) => Ok(Vec::new()), + Err(err) => Err(err), + } + } + + pub async fn remove_from_selection( + &self, + item_ids: Vec, + ) -> Result { + let items = self.remove_from_selection_raw(item_ids).await?; + Ok(summarize_selection(items)) + } + pub async fn get_pad_netlist(&self) -> Result, KiCadError> { let footprint_items = self .get_items_raw(vec![common_types::KiCadObjectType::KotPcbFootprint as i32]) diff --git a/test-scripts/kicad-ipc-cli.rs b/test-scripts/kicad-ipc-cli.rs index fe6cf0a..6f45aa2 100644 --- a/test-scripts/kicad-ipc-cli.rs +++ b/test-scripts/kicad-ipc-cli.rs @@ -87,6 +87,9 @@ enum Command { AddToSelection { item_ids: Vec, }, + RemoveFromSelection { + item_ids: Vec, + }, ClearSelection, SelectionSummary, SelectionDetails, @@ -429,6 +432,13 @@ async fn run() -> Result<(), KiCadError> { println!("type_url={} count={}", entry.type_url, entry.count); } } + Command::RemoveFromSelection { item_ids } => { + let summary = client.remove_from_selection(item_ids).await?; + println!("selection_total={}", summary.total_items); + for entry in summary.type_url_counts { + println!("type_url={} count={}", entry.type_url, entry.count); + } + } Command::ClearSelection => { let summary = client.clear_selection().await?; println!("selection_total={}", summary.total_items); @@ -1191,6 +1201,10 @@ fn parse_args_from(mut args: Vec) -> Result<(CliConfig, Command), KiCadE let item_ids = parse_item_ids(&args[1..], "add-to-selection")?; Command::AddToSelection { item_ids } } + "remove-from-selection" => { + let item_ids = parse_item_ids(&args[1..], "remove-from-selection")?; + Command::RemoveFromSelection { item_ids } + } "clear-selection" => Command::ClearSelection, "selection-summary" => Command::SelectionSummary, "selection-details" => Command::SelectionDetails, @@ -1657,7 +1671,7 @@ fn default_config() -> CliConfig { fn print_help() { println!( - "kicad-ipc-cli\n\nUSAGE:\n cargo run --bin kicad-ipc-cli -- [--socket URI] [--token TOKEN] [--client-name NAME] [--timeout-ms N] [command options]\n\nCOMMANDS:\n ping Check IPC connectivity\n version Fetch KiCad version\n open-docs [--type ] List open docs (default type: pcb)\n project-path Get current project path from open PCB docs\n board-open Exit non-zero if no PCB doc is open\n net-classes List project netclass definitions\n text-variables List text variables for current board document\n expand-text-variables Expand variables in provided text values\n Options: --text (repeatable)\n text-extents Measure text bounding box\n Options: --text \n text-as-shapes Convert text to rendered shapes\n Options: --text (repeatable)\n nets List board nets (requires one open PCB)\n netlist-pads Emit pad-level netlist data (with footprint context)\n items-by-id --id ... Show parsed details for specific item IDs\n item-bbox --id ... Show bounding boxes for item IDs\n hit-test --id --x-nm --y-nm [--tolerance-nm ]\n Hit-test one item at a point\n types-pcb List PCB KiCad object type IDs from proto enum\n items-raw --type-id ... Dump raw Any payloads for requested item type IDs\n items-raw-all-pcb [--debug] Dump all PCB item payloads across all PCB object types\n pad-shape-polygon --pad-id ... --layer-id [--debug]\n Dump pad polygons on a target layer\n padstack-presence --item-id ... --layer-id ... [--debug]\n Check padstack shape presence matrix across layers\n title-block Show title block fields\n board-as-string Dump board as KiCad s-expression text\n selection-as-string Dump current selection as KiCad s-expression text\n stackup Show typed board stackup\n graphics-defaults Show typed graphics defaults\n appearance Show typed editor appearance settings\n set-appearance --inactive-layer-display \n --net-color-display \n --board-flip \n --ratsnest-display \n Set editor appearance settings\n inject-drc-error --severity --message [--x-nm --y-nm ] [--item-id ...]\n Inject a DRC marker (severity: warning|error|exclusion|ignore|info|action|debug|undefined)\n refill-zones [--zone-id ...]\n Refill all zones or a provided subset\n netclass Show typed netclass map for current board nets\n proto-coverage-board-read Print board-read command coverage vs proto\n board-read-report [--out P] Write markdown board reconstruction report\n enabled-layers List enabled board layers\n set-enabled-layers --copper-layer-count [--layer-id ...]\n Set enabled board layer set\n active-layer Show active board layer\n set-active-layer --layer-id \n Set active board layer\n visible-layers Show currently visible board layers\n set-visible-layers --layer-id ...\n Set visible board layers\n board-origin [--type ] Show board origin (`grid` default, or `drill`)\n set-board-origin --type --x-nm --y-nm \n Set board origin (`grid` or `drill`)\n refresh-editor [--frame ] Refresh a specific editor frame (default: pcb)\n begin-commit Start staged commit and print commit ID\n end-commit --id [--action ] [--message ]\n End staged commit with commit/drop action\n add-to-selection --id ...\n Add items to current selection\n clear-selection Clear current item selection\n selection-summary Show current selection item type counts\n selection-details Show parsed details for selected items\n selection-raw Show raw Any payload bytes for selected items\n smoke ping + version + board-open summary\n help Show help\n\nTYPES:\n schematic | symbol | pcb | footprint | drawing-sheet | project\n" + "kicad-ipc-cli\n\nUSAGE:\n cargo run --bin kicad-ipc-cli -- [--socket URI] [--token TOKEN] [--client-name NAME] [--timeout-ms N] [command options]\n\nCOMMANDS:\n ping Check IPC connectivity\n version Fetch KiCad version\n open-docs [--type ] List open docs (default type: pcb)\n project-path Get current project path from open PCB docs\n board-open Exit non-zero if no PCB doc is open\n net-classes List project netclass definitions\n text-variables List text variables for current board document\n expand-text-variables Expand variables in provided text values\n Options: --text (repeatable)\n text-extents Measure text bounding box\n Options: --text \n text-as-shapes Convert text to rendered shapes\n Options: --text (repeatable)\n nets List board nets (requires one open PCB)\n netlist-pads Emit pad-level netlist data (with footprint context)\n items-by-id --id ... Show parsed details for specific item IDs\n item-bbox --id ... Show bounding boxes for item IDs\n hit-test --id --x-nm --y-nm [--tolerance-nm ]\n Hit-test one item at a point\n types-pcb List PCB KiCad object type IDs from proto enum\n items-raw --type-id ... Dump raw Any payloads for requested item type IDs\n items-raw-all-pcb [--debug] Dump all PCB item payloads across all PCB object types\n pad-shape-polygon --pad-id ... --layer-id [--debug]\n Dump pad polygons on a target layer\n padstack-presence --item-id ... --layer-id ... [--debug]\n Check padstack shape presence matrix across layers\n title-block Show title block fields\n board-as-string Dump board as KiCad s-expression text\n selection-as-string Dump current selection as KiCad s-expression text\n stackup Show typed board stackup\n graphics-defaults Show typed graphics defaults\n appearance Show typed editor appearance settings\n set-appearance --inactive-layer-display \n --net-color-display \n --board-flip \n --ratsnest-display \n Set editor appearance settings\n inject-drc-error --severity --message [--x-nm --y-nm ] [--item-id ...]\n Inject a DRC marker (severity: warning|error|exclusion|ignore|info|action|debug|undefined)\n refill-zones [--zone-id ...]\n Refill all zones or a provided subset\n netclass Show typed netclass map for current board nets\n proto-coverage-board-read Print board-read command coverage vs proto\n board-read-report [--out P] Write markdown board reconstruction report\n enabled-layers List enabled board layers\n set-enabled-layers --copper-layer-count [--layer-id ...]\n Set enabled board layer set\n active-layer Show active board layer\n set-active-layer --layer-id \n Set active board layer\n visible-layers Show currently visible board layers\n set-visible-layers --layer-id ...\n Set visible board layers\n board-origin [--type ] Show board origin (`grid` default, or `drill`)\n set-board-origin --type --x-nm --y-nm \n Set board origin (`grid` or `drill`)\n refresh-editor [--frame ] Refresh a specific editor frame (default: pcb)\n begin-commit Start staged commit and print commit ID\n end-commit --id [--action ] [--message ]\n End staged commit with commit/drop action\n add-to-selection --id ...\n Add items to current selection\n remove-from-selection --id ...\n Remove items from current selection\n clear-selection Clear current item selection\n selection-summary Show current selection item type counts\n selection-details Show parsed details for selected items\n selection-raw Show raw Any payload bytes for selected items\n smoke ping + version + board-open summary\n help Show help\n\nTYPES:\n schematic | symbol | pcb | footprint | drawing-sheet | project\n" ); } @@ -2298,6 +2312,25 @@ mod tests { } } + #[test] + fn parse_args_parses_remove_from_selection() { + let (_, command) = parse_args_from(vec![ + "remove-from-selection".to_string(), + "--id".to_string(), + "zone-1".to_string(), + "--id".to_string(), + "zone-2".to_string(), + ]) + .expect("remove-from-selection args should parse"); + + match command { + Command::RemoveFromSelection { item_ids } => { + assert_eq!(item_ids, vec!["zone-1".to_string(), "zone-2".to_string()]); + } + other => panic!("unexpected command variant: {other:?}"), + } + } + #[test] fn parse_args_parses_set_active_layer() { let (_, command) = parse_args_from(vec![