feat: add visible layers and board origin APIs
This commit is contained in:
parent
a109ba7463
commit
54c0bbf7b7
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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> {
|
||||||
|
|
|
||||||
|
|
@ -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};
|
||||||
|
|
|
||||||
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue