From 331910444dd724bc4f6b049e70eab58a09fcfee1 Mon Sep 17 00:00:00 2001 From: Milind Sharma Date: Fri, 20 Feb 2026 16:54:54 +0800 Subject: [PATCH] feat(client): add RefreshEditor API and CLI command --- README.md | 6 +-- docs/TEST_CLI.md | 8 ++++ src/client.rs | 14 ++++++- src/lib.rs | 2 +- src/model/common.rs | 78 ++++++++++++++++++++++++++++++++++- test-scripts/kicad-ipc-cli.rs | 49 ++++++++++++++++++++-- 6 files changed, 147 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 381e177..ba1254b 100644 --- a/README.md +++ b/README.md @@ -39,11 +39,11 @@ Legend: | Section | Proto Commands | Implemented | Coverage | | --- | ---: | ---: | ---: | | Common (base) | 6 | 4 | 67% | -| Common editor/document | 23 | 11 | 48% | +| Common editor/document | 23 | 12 | 52% | | Project manager | 5 | 3 | 60% | | Board editor (PCB) | 22 | 13 | 59% | | Schematic editor (dedicated proto commands) | 0 | 0 | n/a | -| **Total** | **56** | **31** | **55%** | +| **Total** | **56** | **32** | **57%** | ### Common (base) @@ -60,7 +60,7 @@ Legend: | KiCad Command | Status | Rust API | | --- | --- | --- | -| `RefreshEditor` | Not yet | - | +| `RefreshEditor` | Implemented | `KiCadClient::refresh_editor` | | `GetOpenDocuments` | Implemented | `KiCadClient::get_open_documents`, `KiCadClient::get_current_project_path`, `KiCadClient::has_open_board` | | `SaveDocument` | Not yet | - | | `SaveCopyOfDocument` | Not yet | - | diff --git a/docs/TEST_CLI.md b/docs/TEST_CLI.md index 47ba650..bf8d70e 100644 --- a/docs/TEST_CLI.md +++ b/docs/TEST_CLI.md @@ -107,6 +107,14 @@ Show drill origin: cargo run --bin kicad-ipc-cli -- board-origin --type drill ``` +Refresh PCB editor: + +```bash +cargo run --bin kicad-ipc-cli -- refresh-editor --frame pcb +``` + +If your KiCad build does not expose this handler yet, this call may return `AS_UNHANDLED`. + Start a staged commit and print commit ID: ```bash diff --git a/src/client.rs b/src/client.rs index 3c0e0c0..5cca2da 100644 --- a/src/client.rs +++ b/src/client.rs @@ -18,7 +18,7 @@ use crate::model::board::{ Vector2Nm, }; use crate::model::common::{ - CommitAction, CommitSession, DocumentSpecifier, DocumentType, ItemBoundingBox, + CommitAction, CommitSession, DocumentSpecifier, DocumentType, EditorFrameType, ItemBoundingBox, ItemHitTestResult, PcbObjectTypeCode, ProjectInfo, SelectionItemDetail, SelectionSummary, SelectionTypeCount, TextAsShapesEntry, TextAttributesSpec, TextBoxSpec, TextExtents, TextHorizontalAlignment, TextObjectSpec, TextShape, TextShapeGeometry, TextSpec, @@ -42,6 +42,7 @@ const CMD_GET_TEXT_VARIABLES: &str = "kiapi.common.commands.GetTextVariables"; const CMD_EXPAND_TEXT_VARIABLES: &str = "kiapi.common.commands.ExpandTextVariables"; const CMD_GET_TEXT_EXTENTS: &str = "kiapi.common.commands.GetTextExtents"; const CMD_GET_TEXT_AS_SHAPES: &str = "kiapi.common.commands.GetTextAsShapes"; +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"; @@ -293,6 +294,17 @@ impl KiCadClient { Ok(()) } + pub async fn refresh_editor(&self, frame: EditorFrameType) -> Result<(), KiCadError> { + let command = envelope::pack_any( + &common_commands::RefreshEditor { + frame: frame.to_proto(), + }, + CMD_REFRESH_EDITOR, + ); + self.send_command(command).await?; + Ok(()) + } + pub async fn get_version(&self) -> Result { let command = envelope::pack_any(&common_commands::GetVersion {}, CMD_GET_VERSION); let response = self.send_command(command).await?; diff --git a/src/lib.rs b/src/lib.rs index b919033..23bf66e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,7 +33,7 @@ pub use crate::model::board::{ Vector2Nm, }; pub use crate::model::common::{ - CommitAction, CommitSession, DocumentSpecifier, DocumentType, ItemBoundingBox, + CommitAction, CommitSession, DocumentSpecifier, DocumentType, EditorFrameType, ItemBoundingBox, ItemHitTestResult, PcbObjectTypeCode, SelectionItemDetail, SelectionSummary, SelectionTypeCount, TextAsShapesEntry, TextAttributesSpec, TextBoxSpec, TextExtents, TextHorizontalAlignment, TextObjectSpec, TextShape, TextShapeGeometry, TextSpec, diff --git a/src/model/common.rs b/src/model/common.rs index 89f7dc0..d8d4e09 100644 --- a/src/model/common.rs +++ b/src/model/common.rs @@ -12,6 +12,65 @@ pub struct VersionInfo { pub full_version: String, } +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum EditorFrameType { + ProjectManager, + SchematicEditor, + PcbEditor, + SpiceSimulator, + SymbolEditor, + FootprintEditor, + DrawingSheetEditor, +} + +impl EditorFrameType { + pub(crate) fn to_proto(self) -> i32 { + match self { + Self::ProjectManager => common_types::FrameType::FtProjectManager as i32, + Self::SchematicEditor => common_types::FrameType::FtSchematicEditor as i32, + Self::PcbEditor => common_types::FrameType::FtPcbEditor as i32, + Self::SpiceSimulator => common_types::FrameType::FtSpiceSimulator as i32, + Self::SymbolEditor => common_types::FrameType::FtSymbolEditor as i32, + Self::FootprintEditor => common_types::FrameType::FtFootprintEditor as i32, + Self::DrawingSheetEditor => common_types::FrameType::FtDrawingSheetEditor as i32, + } + } +} + +impl std::fmt::Display for EditorFrameType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let value = match self { + Self::ProjectManager => "project-manager", + Self::SchematicEditor => "schematic", + Self::PcbEditor => "pcb", + Self::SpiceSimulator => "spice", + Self::SymbolEditor => "symbol", + Self::FootprintEditor => "footprint", + Self::DrawingSheetEditor => "drawing-sheet", + }; + write!(f, "{value}") + } +} + +impl FromStr for EditorFrameType { + type Err = String; + + fn from_str(value: &str) -> Result { + match value { + "project-manager" => Ok(Self::ProjectManager), + "schematic" => Ok(Self::SchematicEditor), + "pcb" => Ok(Self::PcbEditor), + "spice" => Ok(Self::SpiceSimulator), + "symbol" => Ok(Self::SymbolEditor), + "footprint" => Ok(Self::FootprintEditor), + "drawing-sheet" => Ok(Self::DrawingSheetEditor), + _ => Err(format!( + "unknown frame `{value}`; expected one of: project-manager, schematic, pcb, spice, symbol, footprint, drawing-sheet" + )), + } + } +} + #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum DocumentType { Schematic, @@ -336,7 +395,7 @@ impl std::fmt::Display for ItemHitTestResult { #[cfg(test)] mod tests { - use super::CommitAction; + use super::{CommitAction, EditorFrameType}; use std::str::FromStr; #[test] @@ -349,4 +408,21 @@ mod tests { fn commit_action_rejects_unknown_values() { assert!(CommitAction::from_str("rollback").is_err()); } + + #[test] + fn editor_frame_type_parses_known_values() { + assert_eq!( + EditorFrameType::from_str("pcb"), + Ok(EditorFrameType::PcbEditor) + ); + assert_eq!( + EditorFrameType::from_str("project-manager"), + Ok(EditorFrameType::ProjectManager) + ); + } + + #[test] + fn editor_frame_type_rejects_unknown_values() { + assert!(EditorFrameType::from_str("layout").is_err()); + } } diff --git a/test-scripts/kicad-ipc-cli.rs b/test-scripts/kicad-ipc-cli.rs index 4c434d0..7e1a531 100644 --- a/test-scripts/kicad-ipc-cli.rs +++ b/test-scripts/kicad-ipc-cli.rs @@ -6,9 +6,9 @@ use std::str::FromStr; use std::time::Duration; use kicad_ipc::{ - BoardOriginKind, ClientBuilder, CommitAction, CommitSession, DocumentType, KiCadClient, - KiCadError, PadstackPresenceState, PcbObjectTypeCode, TextObjectSpec, TextShapeGeometry, - TextSpec, Vector2Nm, + BoardOriginKind, ClientBuilder, CommitAction, CommitSession, DocumentType, EditorFrameType, + KiCadClient, KiCadError, PadstackPresenceState, PcbObjectTypeCode, TextObjectSpec, + TextShapeGeometry, TextSpec, Vector2Nm, }; const REPORT_MAX_PAD_NET_ROWS: usize = 2_000; @@ -52,6 +52,9 @@ enum Command { BoardOrigin { kind: BoardOriginKind, }, + RefreshEditor { + frame: EditorFrameType, + }, BeginCommit, EndCommit { id: String, @@ -320,6 +323,10 @@ async fn run() -> Result<(), KiCadError> { kind, origin.x_nm, origin.y_nm ); } + Command::RefreshEditor { frame } => { + client.refresh_editor(frame).await?; + println!("refresh_editor=ok frame={}", frame); + } Command::BeginCommit => { let session = client.begin_commit().await?; println!("commit_id={}", session.id); @@ -795,6 +802,23 @@ fn parse_args_from(mut args: Vec) -> Result<(CliConfig, Command), KiCadE } Command::BoardOrigin { kind } } + "refresh-editor" => { + let mut frame = EditorFrameType::PcbEditor; + let mut i = 1; + while i < args.len() { + if args[i] == "--frame" { + let value = args.get(i + 1).ok_or_else(|| KiCadError::Config { + reason: "missing value for refresh-editor --frame".to_string(), + })?; + frame = EditorFrameType::from_str(value) + .map_err(|err| KiCadError::Config { reason: err })?; + i += 2; + continue; + } + i += 1; + } + Command::RefreshEditor { frame } + } "begin-commit" => Command::BeginCommit, "end-commit" => { let mut id = None; @@ -1154,7 +1178,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 visible-layers Show currently visible board layers\n board-origin [--type ] Show board origin (`grid` default, or `drill`)\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 active-layer Show active board layer\n visible-layers Show currently visible board layers\n board-origin [--type ] Show board origin (`grid` default, 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" ); } @@ -1748,4 +1772,21 @@ mod tests { other => panic!("unexpected command variant: {other:?}"), } } + + #[test] + fn parse_args_parses_refresh_editor_frame() { + let (_, command) = parse_args_from(vec![ + "refresh-editor".to_string(), + "--frame".to_string(), + "schematic".to_string(), + ]) + .expect("refresh-editor args should parse"); + + match command { + Command::RefreshEditor { frame } => { + assert_eq!(frame.to_string(), "schematic"); + } + other => panic!("unexpected command variant: {other:?}"), + } + } }