From e147186fbf3c4270ea8aa8f7763269211a166fcf Mon Sep 17 00:00:00 2001 From: Milind Sharma Date: Fri, 20 Feb 2026 17:20:00 +0800 Subject: [PATCH] feat(client): add SetBoardEnabledLayers API and CLI command --- README.md | 6 +-- docs/TEST_CLI.md | 6 +++ src/client.rs | 36 +++++++++++++-- test-scripts/kicad-ipc-cli.rs | 86 ++++++++++++++++++++++++++++++++++- 4 files changed, 126 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 4934292..e1c14cb 100644 --- a/README.md +++ b/README.md @@ -41,9 +41,9 @@ Legend: | Common (base) | 6 | 4 | 67% | | Common editor/document | 23 | 12 | 52% | | Project manager | 5 | 3 | 60% | -| Board editor (PCB) | 22 | 16 | 73% | +| Board editor (PCB) | 22 | 17 | 77% | | Schematic editor (dedicated proto commands) | 0 | 0 | n/a | -| **Total** | **56** | **35** | **63%** | +| **Total** | **56** | **36** | **64%** | ### Common (base) @@ -101,7 +101,7 @@ Legend: | `GetBoardStackup` | Implemented | `KiCadClient::get_board_stackup_raw`, `KiCadClient::get_board_stackup` | | `UpdateBoardStackup` | Not yet | - | | `GetBoardEnabledLayers` | Implemented | `KiCadClient::get_board_enabled_layers` | -| `SetBoardEnabledLayers` | Not yet | - | +| `SetBoardEnabledLayers` | Implemented | `KiCadClient::set_board_enabled_layers` | | `GetGraphicsDefaults` | Implemented | `KiCadClient::get_graphics_defaults_raw`, `KiCadClient::get_graphics_defaults` | | `GetBoardOrigin` | Implemented | `KiCadClient::get_board_origin` | | `SetBoardOrigin` | Implemented | `KiCadClient::set_board_origin` | diff --git a/docs/TEST_CLI.md b/docs/TEST_CLI.md index ecab9e4..525622d 100644 --- a/docs/TEST_CLI.md +++ b/docs/TEST_CLI.md @@ -83,6 +83,12 @@ List enabled board layers: cargo run --bin kicad-ipc-cli -- enabled-layers ``` +Set enabled board layers: + +```bash +cargo run --bin kicad-ipc-cli -- set-enabled-layers --copper-layer-count 2 --layer-id 47 --layer-id 52 +``` + Show active layer: ```bash diff --git a/src/client.rs b/src/client.rs index 0011ba8..e5fbf04 100644 --- a/src/client.rs +++ b/src/client.rs @@ -46,6 +46,7 @@ const CMD_REFRESH_EDITOR: &str = "kiapi.common.commands.RefreshEditor"; const CMD_GET_OPEN_DOCUMENTS: &str = "kiapi.common.commands.GetOpenDocuments"; const CMD_GET_NETS: &str = "kiapi.board.commands.GetNets"; const CMD_GET_BOARD_ENABLED_LAYERS: &str = "kiapi.board.commands.GetBoardEnabledLayers"; +const CMD_SET_BOARD_ENABLED_LAYERS: &str = "kiapi.board.commands.SetBoardEnabledLayers"; const CMD_GET_ACTIVE_LAYER: &str = "kiapi.board.commands.GetActiveLayer"; const CMD_SET_ACTIVE_LAYER: &str = "kiapi.board.commands.SetActiveLayer"; const CMD_GET_VISIBLE_LAYERS: &str = "kiapi.board.commands.GetVisibleLayers"; @@ -564,10 +565,28 @@ impl KiCadClient { let payload: board_commands::BoardEnabledLayersResponse = envelope::unpack_any(&response, RES_GET_BOARD_ENABLED_LAYERS)?; - Ok(BoardEnabledLayers { - copper_layer_count: payload.copper_layer_count, - layers: payload.layers.into_iter().map(layer_to_model).collect(), - }) + Ok(map_board_enabled_layers_response(payload)) + } + + pub async fn set_board_enabled_layers( + &self, + copper_layer_count: u32, + layer_ids: Vec, + ) -> Result { + let board = self.current_board_document_proto().await?; + let command = board_commands::SetBoardEnabledLayers { + board: Some(board), + copper_layer_count, + layers: layer_ids, + }; + + let response = self + .send_command(envelope::pack_any(&command, CMD_SET_BOARD_ENABLED_LAYERS)) + .await?; + + let payload: board_commands::BoardEnabledLayersResponse = + envelope::unpack_any(&response, RES_GET_BOARD_ENABLED_LAYERS)?; + Ok(map_board_enabled_layers_response(payload)) } pub async fn get_active_layer(&self) -> Result { @@ -1618,6 +1637,15 @@ fn layer_to_model(layer_id: i32) -> BoardLayerInfo { BoardLayerInfo { id: layer_id, name } } +fn map_board_enabled_layers_response( + payload: board_commands::BoardEnabledLayersResponse, +) -> BoardEnabledLayers { + BoardEnabledLayers { + copper_layer_count: payload.copper_layer_count, + layers: payload.layers.into_iter().map(layer_to_model).collect(), + } +} + fn board_origin_kind_to_proto(kind: BoardOriginKind) -> i32 { match kind { BoardOriginKind::Grid => board_commands::BoardOriginType::BotGrid as i32, diff --git a/test-scripts/kicad-ipc-cli.rs b/test-scripts/kicad-ipc-cli.rs index 418ea1e..806bb99 100644 --- a/test-scripts/kicad-ipc-cli.rs +++ b/test-scripts/kicad-ipc-cli.rs @@ -47,6 +47,10 @@ enum Command { }, Nets, EnabledLayers, + SetEnabledLayers { + copper_layer_count: u32, + layer_ids: Vec, + }, ActiveLayer, SetActiveLayer { layer_id: i32, @@ -310,6 +314,18 @@ async fn run() -> Result<(), KiCadError> { println!("layer_id={} layer_name={}", layer.id, layer.name); } } + Command::SetEnabledLayers { + copper_layer_count, + layer_ids, + } => { + let enabled = client + .set_board_enabled_layers(copper_layer_count, layer_ids) + .await?; + println!("copper_layer_count={}", enabled.copper_layer_count); + for layer in enabled.layers { + println!("layer_id={} layer_name={}", layer.id, layer.name); + } + } Command::ActiveLayer => { let layer = client.get_active_layer().await?; println!( @@ -808,6 +824,49 @@ fn parse_args_from(mut args: Vec) -> Result<(CliConfig, Command), KiCadE } "nets" => Command::Nets, "enabled-layers" => Command::EnabledLayers, + "set-enabled-layers" => { + let mut copper_layer_count = None; + let mut layer_ids = Vec::new(); + let mut i = 1; + while i < args.len() { + match args[i].as_str() { + "--copper-layer-count" => { + let value = args.get(i + 1).ok_or_else(|| KiCadError::Config { + reason: "missing value for set-enabled-layers --copper-layer-count" + .to_string(), + })?; + copper_layer_count = + Some(value.parse::().map_err(|err| KiCadError::Config { + reason: format!( + "invalid set-enabled-layers --copper-layer-count `{value}`: {err}" + ), + })?); + i += 2; + } + "--layer-id" => { + let value = args.get(i + 1).ok_or_else(|| KiCadError::Config { + reason: "missing value for set-enabled-layers --layer-id".to_string(), + })?; + layer_ids.push(value.parse::().map_err(|err| KiCadError::Config { + reason: format!( + "invalid set-enabled-layers --layer-id `{value}`: {err}" + ), + })?); + i += 2; + } + _ => { + i += 1; + } + } + } + + Command::SetEnabledLayers { + copper_layer_count: copper_layer_count.ok_or_else(|| KiCadError::Config { + reason: "set-enabled-layers requires `--copper-layer-count `".to_string(), + })?, + layer_ids, + } + } "active-layer" => Command::ActiveLayer, "set-active-layer" => { let mut layer_id = None; @@ -1299,7 +1358,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 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 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 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 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 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" ); } @@ -1926,6 +1985,31 @@ mod tests { } } + #[test] + fn parse_args_parses_set_enabled_layers() { + let (_, command) = parse_args_from(vec![ + "set-enabled-layers".to_string(), + "--copper-layer-count".to_string(), + "2".to_string(), + "--layer-id".to_string(), + "47".to_string(), + "--layer-id".to_string(), + "52".to_string(), + ]) + .expect("set-enabled-layers args should parse"); + + match command { + Command::SetEnabledLayers { + copper_layer_count, + layer_ids, + } => { + assert_eq!(copper_layer_count, 2); + assert_eq!(layer_ids, vec![47, 52]); + } + other => panic!("unexpected command variant: {other:?}"), + } + } + #[test] fn parse_args_parses_set_visible_layers() { let (_, command) = parse_args_from(vec![