feat: add visible layers and board origin APIs

This commit is contained in:
Milind Sharma 2026-02-18 23:34:18 +08:00
parent a109ba7463
commit 54c0bbf7b7
5 changed files with 168 additions and 4 deletions

View File

@ -59,6 +59,24 @@ Show active layer:
cargo run --bin kicad-ipc-cli -- active-layer cargo run --bin kicad-ipc-cli -- active-layer
``` ```
Show visible layers:
```bash
cargo run --bin kicad-ipc-cli -- visible-layers
```
Show board origin (grid origin by default):
```bash
cargo run --bin kicad-ipc-cli -- board-origin
```
Show drill origin:
```bash
cargo run --bin kicad-ipc-cli -- board-origin --type drill
```
Get current project path (derived from open PCB docs): Get current project path (derived from open PCB docs):
```bash ```bash

View File

@ -5,7 +5,9 @@ use std::time::{Duration, SystemTime, UNIX_EPOCH};
use crate::envelope; use crate::envelope;
use crate::error::KiCadError; use crate::error::KiCadError;
use crate::model::board::{BoardEnabledLayers, BoardLayerInfo, BoardNet}; use crate::model::board::{
BoardEnabledLayers, BoardLayerInfo, BoardNet, BoardOriginKind, Vector2Nm,
};
use crate::model::common::{DocumentSpecifier, DocumentType, ProjectInfo, VersionInfo}; use crate::model::common::{DocumentSpecifier, DocumentType, ProjectInfo, VersionInfo};
use crate::proto::kiapi::board::commands as board_commands; use crate::proto::kiapi::board::commands as board_commands;
use crate::proto::kiapi::board::types as board_types; use crate::proto::kiapi::board::types as board_types;
@ -22,12 +24,16 @@ const CMD_GET_OPEN_DOCUMENTS: &str = "kiapi.common.commands.GetOpenDocuments";
const CMD_GET_NETS: &str = "kiapi.board.commands.GetNets"; const CMD_GET_NETS: &str = "kiapi.board.commands.GetNets";
const CMD_GET_BOARD_ENABLED_LAYERS: &str = "kiapi.board.commands.GetBoardEnabledLayers"; const CMD_GET_BOARD_ENABLED_LAYERS: &str = "kiapi.board.commands.GetBoardEnabledLayers";
const CMD_GET_ACTIVE_LAYER: &str = "kiapi.board.commands.GetActiveLayer"; const CMD_GET_ACTIVE_LAYER: &str = "kiapi.board.commands.GetActiveLayer";
const CMD_GET_VISIBLE_LAYERS: &str = "kiapi.board.commands.GetVisibleLayers";
const CMD_GET_BOARD_ORIGIN: &str = "kiapi.board.commands.GetBoardOrigin";
const RES_GET_VERSION: &str = "kiapi.common.commands.GetVersionResponse"; const RES_GET_VERSION: &str = "kiapi.common.commands.GetVersionResponse";
const RES_GET_OPEN_DOCUMENTS: &str = "kiapi.common.commands.GetOpenDocumentsResponse"; const RES_GET_OPEN_DOCUMENTS: &str = "kiapi.common.commands.GetOpenDocumentsResponse";
const RES_GET_NETS: &str = "kiapi.board.commands.NetsResponse"; const RES_GET_NETS: &str = "kiapi.board.commands.NetsResponse";
const RES_GET_BOARD_ENABLED_LAYERS: &str = "kiapi.board.commands.BoardEnabledLayersResponse"; const RES_GET_BOARD_ENABLED_LAYERS: &str = "kiapi.board.commands.BoardEnabledLayersResponse";
const RES_BOARD_LAYER_RESPONSE: &str = "kiapi.board.commands.BoardLayerResponse"; const RES_BOARD_LAYER_RESPONSE: &str = "kiapi.board.commands.BoardLayerResponse";
const RES_BOARD_LAYERS: &str = "kiapi.board.commands.BoardLayers";
const RES_VECTOR2: &str = "kiapi.common.types.Vector2";
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct KiCadClient { pub struct KiCadClient {
@ -251,6 +257,38 @@ impl KiCadClient {
Ok(layer_to_model(payload.layer)) Ok(layer_to_model(payload.layer))
} }
pub async fn get_visible_layers(&self) -> Result<Vec<BoardLayerInfo>, KiCadError> {
let board = self.current_board_document_proto().await?;
let command = board_commands::GetVisibleLayers { board: Some(board) };
let response = self
.send_command(envelope::pack_any(&command, CMD_GET_VISIBLE_LAYERS))
.await?;
let payload: board_commands::BoardLayers =
envelope::unpack_any(&response, RES_BOARD_LAYERS)?;
Ok(payload.layers.into_iter().map(layer_to_model).collect())
}
pub async fn get_board_origin(&self, kind: BoardOriginKind) -> Result<Vector2Nm, KiCadError> {
let board = self.current_board_document_proto().await?;
let command = board_commands::GetBoardOrigin {
board: Some(board),
r#type: board_origin_kind_to_proto(kind),
};
let response = self
.send_command(envelope::pack_any(&command, CMD_GET_BOARD_ORIGIN))
.await?;
let payload: common_types::Vector2 = envelope::unpack_any(&response, RES_VECTOR2)?;
Ok(Vector2Nm {
x_nm: payload.x_nm,
y_nm: payload.y_nm,
})
}
async fn send_command( async fn send_command(
&self, &self,
command: prost_types::Any, command: prost_types::Any,
@ -357,6 +395,13 @@ fn layer_to_model(layer_id: i32) -> BoardLayerInfo {
BoardLayerInfo { id: layer_id, name } BoardLayerInfo { id: layer_id, name }
} }
fn board_origin_kind_to_proto(kind: BoardOriginKind) -> i32 {
match kind {
BoardOriginKind::Grid => board_commands::BoardOriginType::BotGrid as i32,
BoardOriginKind::Drill => board_commands::BoardOriginType::BotDrill as i32,
}
}
fn select_single_board_document( fn select_single_board_document(
docs: &[DocumentSpecifier], docs: &[DocumentSpecifier],
) -> Result<&DocumentSpecifier, KiCadError> { ) -> Result<&DocumentSpecifier, KiCadError> {

View File

@ -20,5 +20,7 @@ pub(crate) mod proto;
pub use crate::client::{ClientBuilder, KiCadClient}; pub use crate::client::{ClientBuilder, KiCadClient};
pub use crate::error::KiCadError; pub use crate::error::KiCadError;
pub use crate::model::board::{BoardEnabledLayers, BoardLayerInfo, BoardNet}; pub use crate::model::board::{
BoardEnabledLayers, BoardLayerInfo, BoardNet, BoardOriginKind, Vector2Nm,
};
pub use crate::model::common::{DocumentSpecifier, DocumentType, VersionInfo}; pub use crate::model::common::{DocumentSpecifier, DocumentType, VersionInfo};

View File

@ -1,3 +1,5 @@
use std::str::FromStr;
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
pub struct BoardNet { pub struct BoardNet {
pub code: i32, pub code: i32,
@ -15,3 +17,63 @@ pub struct BoardEnabledLayers {
pub copper_layer_count: u32, pub copper_layer_count: u32,
pub layers: Vec<BoardLayerInfo>, pub layers: Vec<BoardLayerInfo>,
} }
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum BoardOriginKind {
Grid,
Drill,
}
impl FromStr for BoardOriginKind {
type Err = String;
fn from_str(value: &str) -> Result<Self, Self::Err> {
match value {
"grid" => Ok(Self::Grid),
"drill" => Ok(Self::Drill),
_ => Err(format!(
"unknown board origin kind `{value}`; expected `grid` or `drill`"
)),
}
}
}
impl std::fmt::Display for BoardOriginKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Grid => write!(f, "grid"),
Self::Drill => write!(f, "drill"),
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Vector2Nm {
pub x_nm: i64,
pub y_nm: i64,
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use super::BoardOriginKind;
#[test]
fn board_origin_kind_parses_known_values() {
assert_eq!(
BoardOriginKind::from_str("grid").expect("grid should parse"),
BoardOriginKind::Grid
);
assert_eq!(
BoardOriginKind::from_str("drill").expect("drill should parse"),
BoardOriginKind::Drill
);
}
#[test]
fn board_origin_kind_rejects_unknown_values() {
let result = BoardOriginKind::from_str("other");
assert!(result.is_err());
}
}

View File

@ -2,7 +2,7 @@ use std::process::ExitCode;
use std::str::FromStr; use std::str::FromStr;
use std::time::Duration; use std::time::Duration;
use kicad_ipc::{ClientBuilder, DocumentType, KiCadError}; use kicad_ipc::{BoardOriginKind, ClientBuilder, DocumentType, KiCadError};
#[derive(Debug)] #[derive(Debug)]
struct CliConfig { struct CliConfig {
@ -21,6 +21,8 @@ enum Command {
Nets, Nets,
EnabledLayers, EnabledLayers,
ActiveLayer, ActiveLayer,
VisibleLayers,
BoardOrigin { kind: BoardOriginKind },
Smoke, Smoke,
Help, Help,
} }
@ -139,6 +141,23 @@ async fn run() -> Result<(), KiCadError> {
layer.id, layer.name layer.id, layer.name
); );
} }
Command::VisibleLayers => {
let layers = client.get_visible_layers().await?;
if layers.is_empty() {
println!("no visible layers returned");
} else {
for layer in layers {
println!("layer_id={} layer_name={}", layer.id, layer.name);
}
}
}
Command::BoardOrigin { kind } => {
let origin = client.get_board_origin(kind).await?;
println!(
"origin_kind={} x_nm={} y_nm={}",
kind, origin.x_nm, origin.y_nm
);
}
Command::Smoke => { Command::Smoke => {
client.ping().await?; client.ping().await?;
let version = client.get_version().await?; let version = client.get_version().await?;
@ -208,6 +227,24 @@ fn parse_args() -> Result<(CliConfig, Command), KiCadError> {
"nets" => Command::Nets, "nets" => Command::Nets,
"enabled-layers" => Command::EnabledLayers, "enabled-layers" => Command::EnabledLayers,
"active-layer" => Command::ActiveLayer, "active-layer" => Command::ActiveLayer,
"visible-layers" => Command::VisibleLayers,
"board-origin" => {
let mut kind = BoardOriginKind::Grid;
let mut i = 1;
while i < args.len() {
if args[i] == "--type" {
let value = args.get(i + 1).ok_or_else(|| KiCadError::Config {
reason: "missing value for board-origin --type".to_string(),
})?;
kind = BoardOriginKind::from_str(value)
.map_err(|err| KiCadError::Config { reason: err })?;
i += 2;
continue;
}
i += 1;
}
Command::BoardOrigin { kind }
}
"smoke" => Command::Smoke, "smoke" => Command::Smoke,
"open-docs" => { "open-docs" => {
let mut document_type = DocumentType::Pcb; let mut document_type = DocumentType::Pcb;
@ -246,6 +283,6 @@ fn default_config() -> CliConfig {
fn print_help() { fn print_help() {
println!( println!(
"kicad-ipc-cli\n\nUSAGE:\n cargo run --bin kicad-ipc-cli -- [--socket URI] [--token TOKEN] [--timeout-ms N] <command> [command options]\n\nCOMMANDS:\n ping Check IPC connectivity\n version Fetch KiCad version\n open-docs [--type <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 nets List board nets (requires one open PCB)\n enabled-layers List enabled board layers\n active-layer Show active board layer\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] [--timeout-ms N] <command> [command options]\n\nCOMMANDS:\n ping Check IPC connectivity\n version Fetch KiCad version\n open-docs [--type <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 nets List board nets (requires one open PCB)\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 <t>] Show board origin (`grid` default, or `drill`)\n smoke ping + version + board-open summary\n help Show help\n\nTYPES:\n schematic | symbol | pcb | footprint | drawing-sheet | project\n"
); );
} }