Merge pull request #5 from sync-wrapper-parity

feat(blocking): ship full sync wrapper parity
This commit is contained in:
Milind Sharma 2026-02-21 17:34:17 +08:00 committed by GitHub
commit 853b7ed9d9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 915 additions and 245 deletions

View File

@ -11,6 +11,8 @@ keywords = ["kicad", "eda", "pcb", "ipc"]
categories = ["api-bindings", "asynchronous"]
include = [
"/src/**",
"/test-scripts/kicad-ipc-cli.rs",
"/docs/TEST_CLI.md",
"/README.md",
"/LICENSE",
"/Cargo.toml",
@ -22,6 +24,11 @@ async = ["dep:nng", "dep:prost", "dep:prost-types", "dep:tokio"]
blocking = ["async"]
tracing = ["dep:tracing"]
[[bin]]
name = "kicad-ipc-cli"
path = "test-scripts/kicad-ipc-cli.rs"
required-features = ["blocking"]
[dependencies]
nng = { version = "1.0.1", optional = true }
prost = { version = "0.14.3", optional = true }

View File

@ -8,13 +8,66 @@ Maintainer workflow: see `CONTRIBUTIONS.md`.
## Status
Alpha. `v0.1.0` release candidate.
Alpha. `v0.1.1` released.
- Async API: implemented and usable.
- Sync/blocking wrapper API: planned, not shipped yet.
- Async API (default): implemented and usable.
- Sync/blocking wrapper API (`feature = "blocking"`): implemented with full async parity.
- Real-world user testing: still limited.
- Issues and PRs welcome.
## Usage
### Async API (Default)
`Cargo.toml`:
```toml
[dependencies]
kicad-ipc-rs = "0.1.1"
tokio = { version = "1", features = ["macros", "rt"] }
```
```rust
use kicad_ipc_rs::KiCadClient;
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), kicad_ipc_rs::KiCadError> {
let client = KiCadClient::connect().await?;
client.ping().await?;
let version = client.get_version().await?;
println!("KiCad: {}", version.full_version);
Ok(())
}
```
### Sync API (Blocking)
Enable the `blocking` feature and use `KiCadClientBlocking` for synchronous callers:
`Cargo.toml`:
```toml
[dependencies]
kicad-ipc-rs = { version = "0.1.1", features = ["blocking"] }
```
```rust
use kicad_ipc_rs::KiCadClientBlocking;
fn main() -> Result<(), kicad_ipc_rs::KiCadError> {
let client = KiCadClientBlocking::builder().connect()?;
client.ping()?;
let version = client.get_version()?;
println!("KiCad: {}", version.full_version);
Ok(())
}
```
Implementation notes:
- Blocking calls run through a dedicated Tokio runtime thread.
- Requests are serialized through a bounded queue.
- Runtime teardown is graceful: in-flight work drains before worker exit.
## Protobuf Source
This crate ships checked-in Rust protobuf output under `src/proto/generated/`.
@ -34,7 +87,8 @@ The regeneration tool also stamps `KICAD_API_VERSION` from the KiCad submodule g
## Local Testing
- CLI runbook: `/Users/milindsharma/Developer/kicad-oss/kicad-ipc-rust/docs/TEST_CLI.md`
- CLI runbook: `/Users/milindsharma/Developer/kicad-oss/kicad-ipc-rs/docs/TEST_CLI.md`
- CLI help: `cargo run --features blocking --bin kicad-ipc-cli -- help`
## Runtime Compatibility Notes
@ -169,7 +223,6 @@ Legend:
## Roadmap
`v0.2.0` target:
- Add full sync/blocking wrapper API parity over async client.
- Expand runtime + integration testing coverage.
- Set up CI to run checks/tests on commits and PRs.
- Continue API hardening/docs/examples for stable `1.0` path.

View File

@ -6,9 +6,11 @@ CLI binary path:
Run help:
```bash
cargo run --bin kicad-ipc-cli -- help
cargo run --features blocking --bin kicad-ipc-cli -- help
```
The CLI uses `KiCadClientBlocking` and validates the sync wrapper end-to-end.
## Prereqs
1. KiCad running.
@ -20,145 +22,145 @@ cargo run --bin kicad-ipc-cli -- help
Ping:
```bash
cargo run --bin kicad-ipc-cli -- ping
cargo run --features blocking --bin kicad-ipc-cli -- ping
```
Version:
```bash
cargo run --bin kicad-ipc-cli -- version
cargo run --features blocking --bin kicad-ipc-cli -- version
```
Resolve KiCad binary path (default `kicad-cli`):
```bash
cargo run --bin kicad-ipc-cli -- kicad-binary-path --binary-name kicad-cli
cargo run --features blocking --bin kicad-ipc-cli -- kicad-binary-path --binary-name kicad-cli
```
Resolve plugin settings path (default identifier `kicad-ipc-rust`):
```bash
cargo run --bin kicad-ipc-cli -- plugin-settings-path --identifier kicad-ipc-rust
cargo run --features blocking --bin kicad-ipc-cli -- plugin-settings-path --identifier kicad-ipc-rust
```
List open PCB docs:
```bash
cargo run --bin kicad-ipc-cli -- open-docs --type pcb
cargo run --features blocking --bin kicad-ipc-cli -- open-docs --type pcb
```
Check board open:
```bash
cargo run --bin kicad-ipc-cli -- board-open
cargo run --features blocking --bin kicad-ipc-cli -- board-open
```
List nets:
```bash
cargo run --bin kicad-ipc-cli -- nets
cargo run --features blocking --bin kicad-ipc-cli -- nets
```
List project net classes:
```bash
cargo run --bin kicad-ipc-cli -- net-classes
cargo run --features blocking --bin kicad-ipc-cli -- net-classes
```
Write current net classes back with selected merge mode:
```bash
cargo run --bin kicad-ipc-cli -- set-net-classes --merge-mode merge
cargo run --features blocking --bin kicad-ipc-cli -- set-net-classes --merge-mode merge
```
List text variables for current board document:
```bash
cargo run --bin kicad-ipc-cli -- text-variables
cargo run --features blocking --bin kicad-ipc-cli -- text-variables
```
Set text variables:
```bash
cargo run --bin kicad-ipc-cli -- set-text-variables --merge-mode merge --var REV=A
cargo run --features blocking --bin kicad-ipc-cli -- set-text-variables --merge-mode merge --var REV=A
```
Expand text variables in one or more input strings:
```bash
cargo run --bin kicad-ipc-cli -- expand-text-variables --text "${TITLE}" --text "${REVISION}"
cargo run --features blocking --bin kicad-ipc-cli -- expand-text-variables --text "${TITLE}" --text "${REVISION}"
```
Measure text extents:
```bash
cargo run --bin kicad-ipc-cli -- text-extents --text "R1"
cargo run --features blocking --bin kicad-ipc-cli -- text-extents --text "R1"
```
Convert text to shape primitives:
```bash
cargo run --bin kicad-ipc-cli -- text-as-shapes --text "R1" --text "C5"
cargo run --features blocking --bin kicad-ipc-cli -- text-as-shapes --text "R1" --text "C5"
```
List enabled board layers:
```bash
cargo run --bin kicad-ipc-cli -- enabled-layers
cargo run --features blocking --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
cargo run --features blocking --bin kicad-ipc-cli -- set-enabled-layers --copper-layer-count 2 --layer-id 47 --layer-id 52
```
Show active layer:
```bash
cargo run --bin kicad-ipc-cli -- active-layer
cargo run --features blocking --bin kicad-ipc-cli -- active-layer
```
Set active layer:
```bash
cargo run --bin kicad-ipc-cli -- set-active-layer --layer-id 0
cargo run --features blocking --bin kicad-ipc-cli -- set-active-layer --layer-id 0
```
Show visible layers:
```bash
cargo run --bin kicad-ipc-cli -- visible-layers
cargo run --features blocking --bin kicad-ipc-cli -- visible-layers
```
Set visible layers:
```bash
cargo run --bin kicad-ipc-cli -- set-visible-layers --layer-id 0 --layer-id 31
cargo run --features blocking --bin kicad-ipc-cli -- set-visible-layers --layer-id 0 --layer-id 31
```
Show board origin (grid origin by default):
```bash
cargo run --bin kicad-ipc-cli -- board-origin
cargo run --features blocking --bin kicad-ipc-cli -- board-origin
```
Show drill origin:
```bash
cargo run --bin kicad-ipc-cli -- board-origin --type drill
cargo run --features blocking --bin kicad-ipc-cli -- board-origin --type drill
```
Set board origin:
```bash
cargo run --bin kicad-ipc-cli -- set-board-origin --type grid --x-nm 1000000 --y-nm 2000000
cargo run --features blocking --bin kicad-ipc-cli -- set-board-origin --type grid --x-nm 1000000 --y-nm 2000000
```
Refresh PCB editor:
```bash
cargo run --bin kicad-ipc-cli -- refresh-editor --frame pcb
cargo run --features blocking --bin kicad-ipc-cli -- refresh-editor --frame pcb
```
If your KiCad build does not expose this handler yet, this call may return `AS_UNHANDLED`.
@ -166,226 +168,226 @@ If your KiCad build does not expose this handler yet, this call may return `AS_U
Start a staged commit and print commit ID:
```bash
cargo run --bin kicad-ipc-cli -- --client-name write-test begin-commit
cargo run --features blocking --bin kicad-ipc-cli -- --client-name write-test begin-commit
```
End a staged commit:
```bash
cargo run --bin kicad-ipc-cli -- --client-name write-test end-commit --id <commit-id> --action drop --message "cli test cleanup"
cargo run --features blocking --bin kicad-ipc-cli -- --client-name write-test end-commit --id <commit-id> --action drop --message "cli test cleanup"
```
Save current board document:
```bash
cargo run --bin kicad-ipc-cli -- save-doc
cargo run --features blocking --bin kicad-ipc-cli -- save-doc
```
Save a copy of current board document:
```bash
cargo run --bin kicad-ipc-cli -- save-copy --path /tmp/example.kicad_pcb --overwrite --include-project
cargo run --features blocking --bin kicad-ipc-cli -- save-copy --path /tmp/example.kicad_pcb --overwrite --include-project
```
Revert current board document from disk:
```bash
cargo run --bin kicad-ipc-cli -- revert-doc
cargo run --features blocking --bin kicad-ipc-cli -- revert-doc
```
Run a raw KiCad tool action:
```bash
cargo run --bin kicad-ipc-cli -- run-action --action pcbnew.InteractiveSelection.ClearSelection
cargo run --features blocking --bin kicad-ipc-cli -- run-action --action pcbnew.InteractiveSelection.ClearSelection
```
Create raw Any item payload(s):
```bash
cargo run --bin kicad-ipc-cli -- create-items --item type.googleapis.com/kiapi.board.types.Text=<hex_payload>
cargo run --features blocking --bin kicad-ipc-cli -- create-items --item type.googleapis.com/kiapi.board.types.Text=<hex_payload>
```
Update raw Any item payload(s):
```bash
cargo run --bin kicad-ipc-cli -- update-items --item type.googleapis.com/kiapi.board.types.Text=<hex_payload>
cargo run --features blocking --bin kicad-ipc-cli -- update-items --item type.googleapis.com/kiapi.board.types.Text=<hex_payload>
```
Delete items by ID:
```bash
cargo run --bin kicad-ipc-cli -- delete-items --id <uuid> --id <uuid>
cargo run --features blocking --bin kicad-ipc-cli -- delete-items --id <uuid> --id <uuid>
```
Parse and create items from s-expression:
```bash
cargo run --bin kicad-ipc-cli -- parse-create-items --contents "(kicad_pcb (version 20240108))"
cargo run --features blocking --bin kicad-ipc-cli -- parse-create-items --contents "(kicad_pcb (version 20240108))"
```
Show summary of current PCB selection by item type:
```bash
cargo run --bin kicad-ipc-cli -- selection-summary
cargo run --features blocking --bin kicad-ipc-cli -- selection-summary
```
Show parsed details for currently selected items:
```bash
cargo run --bin kicad-ipc-cli -- selection-details
cargo run --features blocking --bin kicad-ipc-cli -- selection-details
```
Show raw protobuf payload bytes for selected items:
```bash
cargo run --bin kicad-ipc-cli -- selection-raw
cargo run --features blocking --bin kicad-ipc-cli -- selection-raw
```
Add items to current selection:
```bash
cargo run --bin kicad-ipc-cli -- add-to-selection --id <uuid> --id <uuid>
cargo run --features blocking --bin kicad-ipc-cli -- add-to-selection --id <uuid> --id <uuid>
```
Remove items from current selection:
```bash
cargo run --bin kicad-ipc-cli -- remove-from-selection --id <uuid> --id <uuid>
cargo run --features blocking --bin kicad-ipc-cli -- remove-from-selection --id <uuid> --id <uuid>
```
Clear current selection:
```bash
cargo run --bin kicad-ipc-cli -- clear-selection
cargo run --features blocking --bin kicad-ipc-cli -- clear-selection
```
Show pad-level netlist entries (footprint/pad/net):
```bash
cargo run --bin kicad-ipc-cli -- netlist-pads
cargo run --features blocking --bin kicad-ipc-cli -- netlist-pads
```
Show parsed details for specific item IDs:
```bash
cargo run --bin kicad-ipc-cli -- items-by-id --id <uuid> --id <uuid>
cargo run --features blocking --bin kicad-ipc-cli -- items-by-id --id <uuid> --id <uuid>
```
Show item bounding boxes:
```bash
cargo run --bin kicad-ipc-cli -- item-bbox --id <uuid>
cargo run --features blocking --bin kicad-ipc-cli -- item-bbox --id <uuid>
```
Include child text in the bounding box (for items such as footprints):
```bash
cargo run --bin kicad-ipc-cli -- item-bbox --id <uuid> --include-text
cargo run --features blocking --bin kicad-ipc-cli -- item-bbox --id <uuid> --include-text
```
Run hit-test on a specific item:
```bash
cargo run --bin kicad-ipc-cli -- hit-test --id <uuid> --x-nm <x> --y-nm <y> --tolerance-nm 0
cargo run --features blocking --bin kicad-ipc-cli -- hit-test --id <uuid> --x-nm <x> --y-nm <y> --tolerance-nm 0
```
List all PCB object type IDs from the proto enum:
```bash
cargo run --bin kicad-ipc-cli -- types-pcb
cargo run --features blocking --bin kicad-ipc-cli -- types-pcb
```
Dump raw item payloads for one or more PCB object type IDs:
```bash
cargo run --bin kicad-ipc-cli -- items-raw --type-id 11 --type-id 13 --debug
cargo run --features blocking --bin kicad-ipc-cli -- items-raw --type-id 11 --type-id 13 --debug
```
Dump raw payloads for all PCB object classes:
```bash
cargo run --bin kicad-ipc-cli -- items-raw-all-pcb --debug
cargo run --features blocking --bin kicad-ipc-cli -- items-raw-all-pcb --debug
```
Check whether pads/vias have flashed padstack shapes on specific layers:
```bash
cargo run --bin kicad-ipc-cli -- padstack-presence --item-id <uuid> --layer-id 3 --layer-id 34 --debug
cargo run --features blocking --bin kicad-ipc-cli -- padstack-presence --item-id <uuid> --layer-id 3 --layer-id 34 --debug
```
Get polygonized pad shape(s) on a specific layer:
```bash
cargo run --bin kicad-ipc-cli -- pad-shape-polygon --pad-id <uuid> --layer-id 3 --debug
cargo run --features blocking --bin kicad-ipc-cli -- pad-shape-polygon --pad-id <uuid> --layer-id 3 --debug
```
Dump board text (KiCad s-expression):
```bash
cargo run --bin kicad-ipc-cli -- board-as-string
cargo run --features blocking --bin kicad-ipc-cli -- board-as-string
```
Dump selection text (KiCad s-expression):
```bash
cargo run --bin kicad-ipc-cli -- selection-as-string
cargo run --features blocking --bin kicad-ipc-cli -- selection-as-string
```
Dump title block fields:
```bash
cargo run --bin kicad-ipc-cli -- title-block
cargo run --features blocking --bin kicad-ipc-cli -- title-block
```
Show typed stackup/graphics/appearance:
```bash
cargo run --bin kicad-ipc-cli -- stackup
cargo run --bin kicad-ipc-cli -- update-stackup
cargo run --bin kicad-ipc-cli -- graphics-defaults
cargo run --bin kicad-ipc-cli -- appearance
cargo run --features blocking --bin kicad-ipc-cli -- stackup
cargo run --features blocking --bin kicad-ipc-cli -- update-stackup
cargo run --features blocking --bin kicad-ipc-cli -- graphics-defaults
cargo run --features blocking --bin kicad-ipc-cli -- appearance
```
Set editor appearance:
```bash
cargo run --bin kicad-ipc-cli -- set-appearance --inactive-layer-display hidden --net-color-display all --board-flip normal --ratsnest-display all-layers
cargo run --features blocking --bin kicad-ipc-cli -- set-appearance --inactive-layer-display hidden --net-color-display all --board-flip normal --ratsnest-display all-layers
```
Inject DRC marker:
```bash
cargo run --bin kicad-ipc-cli -- inject-drc-error --severity error --message "API marker test" --x-nm 1000000 --y-nm 1000000
cargo run --features blocking --bin kicad-ipc-cli -- inject-drc-error --severity error --message "API marker test" --x-nm 1000000 --y-nm 1000000
```
Refill all zones:
```bash
cargo run --bin kicad-ipc-cli -- refill-zones
cargo run --features blocking --bin kicad-ipc-cli -- refill-zones
```
Start interactive move tool for one or more item IDs:
```bash
cargo run --bin kicad-ipc-cli -- interactive-move --id <uuid> --id <uuid>
cargo run --features blocking --bin kicad-ipc-cli -- interactive-move --id <uuid> --id <uuid>
```
Show typed netclass map:
```bash
cargo run --bin kicad-ipc-cli -- netclass
cargo run --features blocking --bin kicad-ipc-cli -- netclass
```
Print proto command coverage status (board read):
```bash
cargo run --bin kicad-ipc-cli -- proto-coverage-board-read
cargo run --features blocking --bin kicad-ipc-cli -- proto-coverage-board-read
```
Generate full board-read reconstruction markdown report:
```bash
cargo run --bin kicad-ipc-cli -- --timeout-ms 60000 board-read-report --out docs/BOARD_READ_REPORT.md
cargo run --features blocking --bin kicad-ipc-cli -- --timeout-ms 60000 board-read-report --out docs/BOARD_READ_REPORT.md
```
Notes:
@ -395,13 +397,13 @@ Notes:
Get current project path (derived from open PCB docs):
```bash
cargo run --bin kicad-ipc-cli -- project-path
cargo run --features blocking --bin kicad-ipc-cli -- project-path
```
Smoke check:
```bash
cargo run --bin kicad-ipc-cli -- smoke
cargo run --features blocking --bin kicad-ipc-cli -- smoke
```
## Common Flags
@ -409,25 +411,25 @@ cargo run --bin kicad-ipc-cli -- smoke
Custom socket:
```bash
cargo run --bin kicad-ipc-cli -- --socket ipc:///tmp/kicad/api.sock ping
cargo run --features blocking --bin kicad-ipc-cli -- --socket ipc:///tmp/kicad/api.sock ping
```
Custom token:
```bash
cargo run --bin kicad-ipc-cli -- --token "$KICAD_API_TOKEN" version
cargo run --features blocking --bin kicad-ipc-cli -- --token "$KICAD_API_TOKEN" version
```
Stable client name (needed when pairing `begin-commit` and `end-commit` across separate CLI runs):
```bash
cargo run --bin kicad-ipc-cli -- --client-name write-test begin-commit
cargo run --features blocking --bin kicad-ipc-cli -- --client-name write-test begin-commit
```
Custom timeout:
```bash
cargo run --bin kicad-ipc-cli -- --timeout-ms 5000 ping
cargo run --features blocking --bin kicad-ipc-cli -- --timeout-ms 5000 ping
```
## Failure Hints

View File

@ -1,22 +1,664 @@
use crate::client::KiCadClient;
use std::collections::BTreeMap;
use std::path::PathBuf;
use std::sync::mpsc::{self, SyncSender};
use std::sync::{Arc, Mutex};
use std::thread::{self, JoinHandle, ThreadId};
use std::time::Duration;
use prost_types::Any;
use crate::client::{ClientBuilder, KiCadClient};
use crate::error::KiCadError;
use crate::model::board::*;
use crate::model::common::*;
const BLOCKING_QUEUE_CAPACITY: usize = 64;
type Job = Box<dyn FnOnce(&tokio::runtime::Runtime) + Send + 'static>;
#[derive(Debug)]
struct BlockingCore {
job_tx: Mutex<Option<SyncSender<Job>>>,
worker_thread_id: ThreadId,
worker_join: Mutex<Option<JoinHandle<()>>>,
}
impl BlockingCore {
fn start() -> Result<Arc<Self>, KiCadError> {
let (job_tx, job_rx) = mpsc::sync_channel::<Job>(BLOCKING_QUEUE_CAPACITY);
let (init_tx, init_rx) = mpsc::sync_channel::<Result<ThreadId, KiCadError>>(1);
let worker_name = format!("kicad-ipc-blocking-runtime-{}", std::process::id());
let worker_join = thread::Builder::new()
.name(worker_name)
.spawn(move || {
let runtime = match tokio::runtime::Builder::new_current_thread()
.enable_time()
.build()
{
Ok(runtime) => runtime,
Err(err) => {
let _ = init_tx.send(Err(KiCadError::RuntimeJoin(err.to_string())));
return;
}
};
let _ = init_tx.send(Ok(thread::current().id()));
for job in job_rx {
job(&runtime);
}
})
.map_err(|err| KiCadError::RuntimeJoin(err.to_string()))?;
let worker_thread_id = init_rx
.recv()
.map_err(|_| KiCadError::BlockingRuntimeClosed)??;
Ok(Arc::new(Self {
job_tx: Mutex::new(Some(job_tx)),
worker_thread_id,
worker_join: Mutex::new(Some(worker_join)),
}))
}
fn shutdown(&self) {
if let Ok(mut tx_guard) = self.job_tx.lock() {
tx_guard.take();
}
let handle = match self.worker_join.lock() {
Ok(mut guard) => guard.take(),
Err(_) => None,
};
if let Some(handle) = handle {
if thread::current().id() != self.worker_thread_id {
let _ = handle.join();
}
}
}
fn call<T, F>(&self, f: F) -> Result<T, KiCadError>
where
T: Send + 'static,
F: FnOnce(&tokio::runtime::Runtime) -> Result<T, KiCadError> + Send + 'static,
{
let sender = {
let guard = self
.job_tx
.lock()
.map_err(|_| KiCadError::BlockingRuntimeClosed)?;
guard
.as_ref()
.cloned()
.ok_or(KiCadError::BlockingRuntimeClosed)?
};
let (result_tx, result_rx) = mpsc::sync_channel::<Result<T, KiCadError>>(1);
sender
.send(Box::new(move |runtime| {
let result = f(runtime);
let _ = result_tx.send(result);
}))
.map_err(|_| KiCadError::BlockingRuntimeClosed)?;
result_rx
.recv()
.map_err(|_| KiCadError::BlockingRuntimeClosed)?
}
}
impl Drop for BlockingCore {
fn drop(&mut self) {
self.shutdown();
}
}
#[derive(Clone, Debug)]
pub struct KiCadClientBlocking {
inner: KiCadClient,
core: Arc<BlockingCore>,
}
#[derive(Clone, Debug)]
pub struct KiCadClientBlockingBuilder {
inner: ClientBuilder,
}
impl KiCadClientBlockingBuilder {
pub fn new() -> Self {
Self {
inner: ClientBuilder::new(),
}
}
pub fn timeout(mut self, timeout: Duration) -> Self {
self.inner = self.inner.timeout(timeout);
self
}
pub fn socket_path(mut self, socket_path: impl Into<String>) -> Self {
self.inner = self.inner.socket_path(socket_path);
self
}
pub fn token(mut self, token: impl Into<String>) -> Self {
self.inner = self.inner.token(token);
self
}
pub fn client_name(mut self, client_name: impl Into<String>) -> Self {
self.inner = self.inner.client_name(client_name);
self
}
pub fn connect(self) -> Result<KiCadClientBlocking, KiCadError> {
let core = BlockingCore::start()?;
let inner_builder = self.inner;
let inner = core.call(move |runtime| runtime.block_on(inner_builder.connect()))?;
Ok(KiCadClientBlocking { inner, core })
}
}
impl Default for KiCadClientBlockingBuilder {
fn default() -> Self {
Self::new()
}
}
macro_rules! blocking_methods {
(
$(fn $name:ident(&self $(, $arg:ident : $arg_ty:ty)*) -> $ret:ty;)+
) => {
$(
pub fn $name(&self, $($arg: $arg_ty),*) -> $ret {
let client = self.inner.clone();
self.core.call(move |runtime| runtime.block_on(async move {
client.$name($($arg),*).await
}))
}
)+
#[cfg(test)]
pub(crate) const GENERATED_BLOCKING_METHOD_NAMES: &'static [&'static str] = &[
$(stringify!($name),)+
];
};
}
impl KiCadClientBlocking {
pub fn builder() -> KiCadClientBlockingBuilder {
KiCadClientBlockingBuilder::new()
}
pub fn connect() -> Result<Self, KiCadError> {
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_time()
.build()
.map_err(|err| KiCadError::RuntimeJoin(err.to_string()))?;
let inner = runtime.block_on(KiCadClient::connect())?;
Ok(Self { inner })
KiCadClientBlockingBuilder::new().connect()
}
pub fn timeout(&self) -> Duration {
self.inner.timeout()
}
pub fn socket_uri(&self) -> &str {
self.inner.socket_uri()
}
pub fn inner(&self) -> &KiCadClient {
&self.inner
}
pub fn run_action_raw(&self, action: impl Into<String>) -> Result<Any, KiCadError> {
let action = action.into();
let client = self.inner.clone();
self.core.call(move |runtime| {
runtime.block_on(async move { client.run_action_raw(action).await })
})
}
pub fn run_action(&self, action: impl Into<String>) -> Result<RunActionStatus, KiCadError> {
let action = action.into();
let client = self.inner.clone();
self.core
.call(move |runtime| runtime.block_on(async move { client.run_action(action).await }))
}
pub fn get_kicad_binary_path_raw(
&self,
binary_name: impl Into<String>,
) -> Result<Any, KiCadError> {
let binary_name = binary_name.into();
let client = self.inner.clone();
self.core.call(move |runtime| {
runtime.block_on(async move { client.get_kicad_binary_path_raw(binary_name).await })
})
}
pub fn get_kicad_binary_path(
&self,
binary_name: impl Into<String>,
) -> Result<String, KiCadError> {
let binary_name = binary_name.into();
let client = self.inner.clone();
self.core.call(move |runtime| {
runtime.block_on(async move { client.get_kicad_binary_path(binary_name).await })
})
}
pub fn get_plugin_settings_path_raw(
&self,
identifier: impl Into<String>,
) -> Result<Any, KiCadError> {
let identifier = identifier.into();
let client = self.inner.clone();
self.core.call(move |runtime| {
runtime.block_on(async move { client.get_plugin_settings_path_raw(identifier).await })
})
}
pub fn get_plugin_settings_path(
&self,
identifier: impl Into<String>,
) -> Result<String, KiCadError> {
let identifier = identifier.into();
let client = self.inner.clone();
self.core.call(move |runtime| {
runtime.block_on(async move { client.get_plugin_settings_path(identifier).await })
})
}
pub fn end_commit_raw(
&self,
session: CommitSession,
action: CommitAction,
message: impl Into<String>,
) -> Result<Any, KiCadError> {
let message = message.into();
let client = self.inner.clone();
self.core.call(move |runtime| {
runtime.block_on(async move { client.end_commit_raw(session, action, message).await })
})
}
pub fn end_commit(
&self,
session: CommitSession,
action: CommitAction,
message: impl Into<String>,
) -> Result<(), KiCadError> {
let message = message.into();
let client = self.inner.clone();
self.core.call(move |runtime| {
runtime.block_on(async move { client.end_commit(session, action, message).await })
})
}
pub fn parse_and_create_items_from_string_raw(
&self,
contents: impl Into<String>,
) -> Result<Any, KiCadError> {
let contents = contents.into();
let client = self.inner.clone();
self.core.call(move |runtime| {
runtime.block_on(async move {
client
.parse_and_create_items_from_string_raw(contents)
.await
})
})
}
pub fn parse_and_create_items_from_string(
&self,
contents: impl Into<String>,
) -> Result<Vec<Any>, KiCadError> {
let contents = contents.into();
let client = self.inner.clone();
self.core.call(move |runtime| {
runtime
.block_on(async move { client.parse_and_create_items_from_string(contents).await })
})
}
pub fn inject_drc_error_raw(
&self,
severity: DrcSeverity,
message: impl Into<String>,
position: Option<Vector2Nm>,
item_ids: Vec<String>,
) -> Result<Any, KiCadError> {
let message = message.into();
let client = self.inner.clone();
self.core.call(move |runtime| {
runtime.block_on(async move {
client
.inject_drc_error_raw(severity, message, position, item_ids)
.await
})
})
}
pub fn inject_drc_error(
&self,
severity: DrcSeverity,
message: impl Into<String>,
position: Option<Vector2Nm>,
item_ids: Vec<String>,
) -> Result<Option<String>, KiCadError> {
let message = message.into();
let client = self.inner.clone();
self.core.call(move |runtime| {
runtime.block_on(async move {
client
.inject_drc_error(severity, message, position, item_ids)
.await
})
})
}
pub fn save_copy_of_document_raw(
&self,
path: impl Into<String>,
overwrite: bool,
include_project: bool,
) -> Result<Any, KiCadError> {
let path = path.into();
let client = self.inner.clone();
self.core.call(move |runtime| {
runtime.block_on(async move {
client
.save_copy_of_document_raw(path, overwrite, include_project)
.await
})
})
}
pub fn save_copy_of_document(
&self,
path: impl Into<String>,
overwrite: bool,
include_project: bool,
) -> Result<(), KiCadError> {
let path = path.into();
let client = self.inner.clone();
self.core.call(move |runtime| {
runtime.block_on(async move {
client
.save_copy_of_document(path, overwrite, include_project)
.await
})
})
}
blocking_methods! {
fn ping(&self) -> Result<(), KiCadError>;
fn refresh_editor(&self, frame: EditorFrameType) -> Result<(), KiCadError>;
fn get_version(&self) -> Result<VersionInfo, KiCadError>;
fn get_open_documents(&self, document_type: DocumentType) -> Result<Vec<DocumentSpecifier>, KiCadError>;
fn get_net_classes_raw(&self) -> Result<Any, KiCadError>;
fn get_net_classes(&self) -> Result<Vec<NetClassInfo>, KiCadError>;
fn set_net_classes_raw(&self, net_classes: Vec<NetClassInfo>, merge_mode: MapMergeMode) -> Result<Any, KiCadError>;
fn set_net_classes(&self, net_classes: Vec<NetClassInfo>, merge_mode: MapMergeMode) -> Result<Vec<NetClassInfo>, KiCadError>;
fn get_text_variables_raw(&self) -> Result<Any, KiCadError>;
fn get_text_variables(&self) -> Result<BTreeMap<String, String>, KiCadError>;
fn set_text_variables_raw(&self, variables: BTreeMap<String, String>, merge_mode: MapMergeMode) -> Result<Any, KiCadError>;
fn set_text_variables(&self, variables: BTreeMap<String, String>, merge_mode: MapMergeMode) -> Result<BTreeMap<String, String>, KiCadError>;
fn expand_text_variables_raw(&self, text: Vec<String>) -> Result<Any, KiCadError>;
fn expand_text_variables(&self, text: Vec<String>) -> Result<Vec<String>, KiCadError>;
fn get_text_extents_raw(&self, text: TextSpec) -> Result<Any, KiCadError>;
fn get_text_extents(&self, text: TextSpec) -> Result<TextExtents, KiCadError>;
fn get_text_as_shapes_raw(&self, text: Vec<TextObjectSpec>) -> Result<Any, KiCadError>;
fn get_text_as_shapes(&self, text: Vec<TextObjectSpec>) -> Result<Vec<TextAsShapesEntry>, KiCadError>;
fn get_current_project_path(&self) -> Result<PathBuf, KiCadError>;
fn has_open_board(&self) -> Result<bool, KiCadError>;
fn begin_commit_raw(&self) -> Result<Any, KiCadError>;
fn begin_commit(&self) -> Result<CommitSession, KiCadError>;
fn create_items_raw(&self, items: Vec<Any>, container_id: Option<String>) -> Result<Any, KiCadError>;
fn create_items(&self, items: Vec<Any>, container_id: Option<String>) -> Result<Vec<Any>, KiCadError>;
fn update_items_raw(&self, items: Vec<Any>) -> Result<Any, KiCadError>;
fn update_items(&self, items: Vec<Any>) -> Result<Vec<Any>, KiCadError>;
fn delete_items_raw(&self, item_ids: Vec<String>) -> Result<Any, KiCadError>;
fn delete_items(&self, item_ids: Vec<String>) -> Result<Vec<String>, KiCadError>;
fn get_nets(&self) -> Result<Vec<BoardNet>, KiCadError>;
fn get_board_enabled_layers(&self) -> Result<BoardEnabledLayers, KiCadError>;
fn set_board_enabled_layers(&self, copper_layer_count: u32, layer_ids: Vec<i32>) -> Result<BoardEnabledLayers, KiCadError>;
fn get_active_layer(&self) -> Result<BoardLayerInfo, KiCadError>;
fn set_active_layer(&self, layer_id: i32) -> Result<(), KiCadError>;
fn get_visible_layers(&self) -> Result<Vec<BoardLayerInfo>, KiCadError>;
fn set_visible_layers(&self, layer_ids: Vec<i32>) -> Result<(), KiCadError>;
fn get_board_origin(&self, kind: BoardOriginKind) -> Result<Vector2Nm, KiCadError>;
fn set_board_origin(&self, kind: BoardOriginKind, origin: Vector2Nm) -> Result<(), KiCadError>;
fn get_selection_summary(&self) -> Result<SelectionSummary, KiCadError>;
fn get_selection_raw(&self) -> Result<Vec<Any>, KiCadError>;
fn get_selection_details(&self) -> Result<Vec<SelectionItemDetail>, KiCadError>;
fn get_selection(&self) -> Result<Vec<PcbItem>, KiCadError>;
fn add_to_selection_raw(&self, item_ids: Vec<String>) -> Result<Vec<Any>, KiCadError>;
fn add_to_selection(&self, item_ids: Vec<String>) -> Result<SelectionSummary, KiCadError>;
fn clear_selection_raw(&self) -> Result<Vec<Any>, KiCadError>;
fn clear_selection(&self) -> Result<SelectionSummary, KiCadError>;
fn remove_from_selection_raw(&self, item_ids: Vec<String>) -> Result<Vec<Any>, KiCadError>;
fn remove_from_selection(&self, item_ids: Vec<String>) -> Result<SelectionSummary, KiCadError>;
fn get_pad_netlist(&self) -> Result<Vec<PadNetEntry>, KiCadError>;
fn get_items_raw_by_type_codes(&self, type_codes: Vec<i32>) -> Result<Vec<Any>, KiCadError>;
fn get_items_details_by_type_codes(&self, type_codes: Vec<i32>) -> Result<Vec<SelectionItemDetail>, KiCadError>;
fn get_items_by_type_codes(&self, type_codes: Vec<i32>) -> Result<Vec<PcbItem>, KiCadError>;
fn get_all_pcb_items_raw(&self) -> Result<Vec<(PcbObjectTypeCode, Vec<Any>)>, KiCadError>;
fn get_all_pcb_items_details(&self) -> Result<Vec<(PcbObjectTypeCode, Vec<SelectionItemDetail>)>, KiCadError>;
fn get_all_pcb_items(&self) -> Result<Vec<(PcbObjectTypeCode, Vec<PcbItem>)>, KiCadError>;
fn get_items_by_net_raw(&self, type_codes: Vec<i32>, net_codes: Vec<i32>) -> Result<Vec<Any>, KiCadError>;
fn get_items_by_net(&self, type_codes: Vec<i32>, net_codes: Vec<i32>) -> Result<Vec<PcbItem>, KiCadError>;
fn get_items_by_net_class_raw(&self, type_codes: Vec<i32>, net_classes: Vec<String>) -> Result<Vec<Any>, KiCadError>;
fn get_items_by_net_class(&self, type_codes: Vec<i32>, net_classes: Vec<String>) -> Result<Vec<PcbItem>, KiCadError>;
fn get_netclass_for_nets_raw(&self, nets: Vec<BoardNet>) -> Result<Any, KiCadError>;
fn get_netclass_for_nets(&self, nets: Vec<BoardNet>) -> Result<Vec<NetClassForNetEntry>, KiCadError>;
fn refill_zones(&self, zone_ids: Vec<String>) -> Result<(), KiCadError>;
fn get_pad_shape_as_polygon_raw(&self, pad_ids: Vec<String>, layer_id: i32) -> Result<Vec<Any>, KiCadError>;
fn get_pad_shape_as_polygon(&self, pad_ids: Vec<String>, layer_id: i32) -> Result<Vec<PadShapeAsPolygonEntry>, KiCadError>;
fn check_padstack_presence_on_layers_raw(&self, item_ids: Vec<String>, layer_ids: Vec<i32>) -> Result<Vec<Any>, KiCadError>;
fn check_padstack_presence_on_layers(&self, item_ids: Vec<String>, layer_ids: Vec<i32>) -> Result<Vec<PadstackPresenceEntry>, KiCadError>;
fn get_board_stackup_raw(&self) -> Result<Any, KiCadError>;
fn get_board_stackup(&self) -> Result<BoardStackup, KiCadError>;
fn update_board_stackup_raw(&self, stackup: BoardStackup) -> Result<Any, KiCadError>;
fn update_board_stackup(&self, stackup: BoardStackup) -> Result<BoardStackup, KiCadError>;
fn get_graphics_defaults_raw(&self) -> Result<Any, KiCadError>;
fn get_graphics_defaults(&self) -> Result<GraphicsDefaults, KiCadError>;
fn get_board_editor_appearance_settings_raw(&self) -> Result<Any, KiCadError>;
fn get_board_editor_appearance_settings(&self) -> Result<BoardEditorAppearanceSettings, KiCadError>;
fn set_board_editor_appearance_settings(&self, settings: BoardEditorAppearanceSettings) -> Result<BoardEditorAppearanceSettings, KiCadError>;
fn interactive_move_items_raw(&self, item_ids: Vec<String>) -> Result<Any, KiCadError>;
fn interactive_move_items(&self, item_ids: Vec<String>) -> Result<(), KiCadError>;
fn get_title_block_info(&self) -> Result<TitleBlockInfo, KiCadError>;
fn save_document_raw(&self) -> Result<Any, KiCadError>;
fn save_document(&self) -> Result<(), KiCadError>;
fn revert_document_raw(&self) -> Result<Any, KiCadError>;
fn revert_document(&self) -> Result<(), KiCadError>;
fn get_board_as_string(&self) -> Result<String, KiCadError>;
fn get_selection_as_string(&self) -> Result<String, KiCadError>;
fn get_items_by_id_raw(&self, item_ids: Vec<String>) -> Result<Vec<Any>, KiCadError>;
fn get_items_by_id_details(&self, item_ids: Vec<String>) -> Result<Vec<SelectionItemDetail>, KiCadError>;
fn get_items_by_id(&self, item_ids: Vec<String>) -> Result<Vec<PcbItem>, KiCadError>;
fn get_item_bounding_boxes(&self, item_ids: Vec<String>, include_child_text: bool) -> Result<Vec<ItemBoundingBox>, KiCadError>;
fn hit_test_item(&self, item_id: String, position: Vector2Nm, tolerance_nm: i32) -> Result<ItemHitTestResult, KiCadError>;
}
#[cfg(test)]
pub(crate) const MANUAL_BLOCKING_METHOD_NAMES: &'static [&'static str] = &[
"connect",
"run_action_raw",
"run_action",
"get_kicad_binary_path_raw",
"get_kicad_binary_path",
"get_plugin_settings_path_raw",
"get_plugin_settings_path",
"end_commit_raw",
"end_commit",
"parse_and_create_items_from_string_raw",
"parse_and_create_items_from_string",
"inject_drc_error_raw",
"inject_drc_error",
"save_copy_of_document_raw",
"save_copy_of_document",
];
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::BTreeSet;
use std::sync::mpsc as std_mpsc;
use std::time::{Duration, Instant};
#[test]
fn blocking_core_executes_job_and_returns_result() {
let core = BlockingCore::start().expect("blocking core must start");
let value = core
.call(|_| Ok::<_, KiCadError>(1234))
.expect("blocking job should execute");
assert_eq!(value, 1234);
}
#[test]
fn blocking_core_handles_concurrent_submitters() {
let core = BlockingCore::start().expect("blocking core must start");
let mut handles = Vec::new();
for idx in 0..8 {
let core = Arc::clone(&core);
handles.push(thread::spawn(move || {
core.call(move |_| Ok::<_, KiCadError>(idx * 2))
.expect("job should return");
}));
}
for handle in handles {
handle.join().expect("submitter thread must join");
}
}
#[test]
fn blocking_core_shutdown_drains_inflight_jobs() {
let core = BlockingCore::start().expect("blocking core must start");
let (started_tx, started_rx) = std_mpsc::sync_channel::<()>(1);
let core_for_call = Arc::clone(&core);
let worker = thread::spawn(move || {
core_for_call
.call(move |_| {
let _ = started_tx.send(());
thread::sleep(Duration::from_millis(120));
Ok::<_, KiCadError>(())
})
.expect("in-flight job should complete");
});
started_rx
.recv_timeout(Duration::from_secs(1))
.expect("job should begin");
let begin = Instant::now();
core.shutdown();
let elapsed = begin.elapsed();
assert!(
elapsed >= Duration::from_millis(80),
"shutdown should wait for in-flight job; elapsed: {elapsed:?}"
);
worker.join().expect("worker submitter should join");
}
#[test]
fn blocking_core_returns_closed_error_after_shutdown() {
let core = BlockingCore::start().expect("blocking core must start");
core.shutdown();
let err = core
.call(|_| Ok::<_, KiCadError>(()))
.expect_err("closed core should reject calls");
assert!(matches!(err, KiCadError::BlockingRuntimeClosed));
}
#[test]
fn sync_wrapper_covers_async_method_names() {
let mut async_methods = BTreeSet::new();
for line in include_str!("client.rs").lines() {
let trimmed = line.trim_start();
if let Some(rest) = trimmed.strip_prefix("pub async fn ") {
if let Some(name) = rest.split('(').next() {
async_methods.insert(name.trim().to_string());
}
}
}
let blocking_methods: BTreeSet<String> =
KiCadClientBlocking::GENERATED_BLOCKING_METHOD_NAMES
.iter()
.chain(KiCadClientBlocking::MANUAL_BLOCKING_METHOD_NAMES.iter())
.map(|name| (*name).to_string())
.collect();
let missing: Vec<String> = async_methods
.into_iter()
.filter(|name| !blocking_methods.contains(name))
.collect();
assert!(
missing.is_empty(),
"missing blocking wrappers for async methods: {:?}",
missing
);
}
#[test]
fn impl_into_string_wrapper_signatures_accept_str() {
fn assert_signatures(client: &KiCadClientBlocking) {
let _ = client.run_action_raw("pcbnew.Refresh");
let _ = client.run_action("pcbnew.Refresh");
let _ = client.get_kicad_binary_path_raw("kicad-cli");
let _ = client.get_kicad_binary_path("kicad-cli");
let _ = client.get_plugin_settings_path_raw("kicad-ipc-rs");
let _ = client.get_plugin_settings_path("kicad-ipc-rs");
let _ = client.end_commit_raw(
CommitSession {
id: "commit-id".to_string(),
},
CommitAction::Drop,
"test",
);
let _ = client.end_commit(
CommitSession {
id: "commit-id".to_string(),
},
CommitAction::Drop,
"test",
);
let _ = client.parse_and_create_items_from_string_raw("(kicad_pcb)");
let _ = client.parse_and_create_items_from_string("(kicad_pcb)");
let _ = client.inject_drc_error_raw(DrcSeverity::Warning, "marker", None, Vec::new());
let _ = client.inject_drc_error(DrcSeverity::Warning, "marker", None, Vec::new());
let _ = client.save_copy_of_document_raw("/tmp/example.kicad_pcb", false, false);
let _ = client.save_copy_of_document("/tmp/example.kicad_pcb", false, false);
}
let _ = assert_signatures as fn(&KiCadClientBlocking);
}
#[test]
fn blocking_smoke_live_when_socket_env_is_set() {
if std::env::var("KICAD_API_SOCKET").is_err() {
return;
}
let client = KiCadClientBlocking::connect().expect("blocking client should connect");
client.ping().expect("ping should succeed");
let _ = client.get_version().expect("version should succeed");
let _ = client
.get_open_documents(DocumentType::Pcb)
.expect("open docs should succeed");
let _ = client
.get_visible_layers()
.expect("board read method should succeed");
}
}

View File

@ -52,6 +52,9 @@ pub enum KiCadError {
#[error("runtime task join failed: {0}")]
RuntimeJoin(String),
#[error("blocking runtime is unavailable")]
BlockingRuntimeClosed,
#[error("mutex poisoned")]
InternalPoisoned,

View File

@ -19,6 +19,8 @@ pub mod blocking;
pub(crate) mod proto;
#[cfg(feature = "blocking")]
pub use crate::blocking::{KiCadClientBlocking, KiCadClientBlockingBuilder};
pub use crate::client::{ClientBuilder, KiCadClient};
pub use crate::error::KiCadError;
pub use crate::kicad_api_version::KICAD_API_VERSION;

View File

@ -5,9 +5,9 @@ use std::process::ExitCode;
use std::str::FromStr;
use std::time::Duration;
use kicad_ipc::{
BoardFlipMode, BoardOriginKind, ClientBuilder, CommitAction, CommitSession, DocumentType,
DrcSeverity, EditorFrameType, InactiveLayerDisplayMode, KiCadClient, KiCadError, MapMergeMode,
use kicad_ipc_rs::{
BoardFlipMode, BoardOriginKind, CommitAction, CommitSession, DocumentType, DrcSeverity,
EditorFrameType, InactiveLayerDisplayMode, KiCadClientBlocking, KiCadError, MapMergeMode,
NetColorDisplayMode, PadstackPresenceState, PcbObjectTypeCode, RatsnestDisplayMode,
TextObjectSpec, TextShapeGeometry, TextSpec, Vector2Nm,
};
@ -190,9 +190,8 @@ enum Command {
Help,
}
#[tokio::main(flavor = "current_thread")]
async fn main() -> ExitCode {
match run().await {
fn main() -> ExitCode {
match run() {
Ok(()) => ExitCode::SUCCESS,
Err(err) => {
eprintln!("error: {err}");
@ -216,7 +215,7 @@ async fn main() -> ExitCode {
}
}
async fn run() -> Result<(), KiCadError> {
fn run() -> Result<(), KiCadError> {
let (config, command) = parse_args()?;
if matches!(command, Command::Help) {
@ -224,7 +223,8 @@ async fn run() -> Result<(), KiCadError> {
return Ok(());
}
let mut builder = ClientBuilder::new().timeout(Duration::from_millis(config.timeout_ms));
let mut builder =
KiCadClientBlocking::builder().timeout(Duration::from_millis(config.timeout_ms));
if let Some(socket) = config.socket {
builder = builder.socket_path(socket);
}
@ -235,30 +235,30 @@ async fn run() -> Result<(), KiCadError> {
builder = builder.client_name(client_name);
}
let client = builder.connect().await?;
let client = builder.connect()?;
match command {
Command::Ping => {
client.ping().await?;
client.ping()?;
println!("pong");
}
Command::Version => {
let version = client.get_version().await?;
let version = client.get_version()?;
println!(
"version: {}.{}.{} ({})",
version.major, version.minor, version.patch, version.full_version
);
}
Command::KiCadBinaryPath { binary_name } => {
let path = client.get_kicad_binary_path(binary_name).await?;
let path = client.get_kicad_binary_path(binary_name)?;
println!("kicad_binary_path={path}");
}
Command::PluginSettingsPath { identifier } => {
let path = client.get_plugin_settings_path(identifier).await?;
let path = client.get_plugin_settings_path(identifier)?;
println!("plugin_settings_path={path}");
}
Command::OpenDocs { document_type } => {
let docs = client.get_open_documents(document_type).await?;
let docs = client.get_open_documents(document_type)?;
if docs.is_empty() {
println!("no open `{document_type}` documents");
} else {
@ -280,11 +280,11 @@ async fn run() -> Result<(), KiCadError> {
}
}
Command::ProjectPath => {
let path = client.get_current_project_path().await?;
let path = client.get_current_project_path()?;
println!("project_path={}", path.display());
}
Command::BoardOpen => {
let has_board = client.has_open_board().await?;
let has_board = client.has_open_board()?;
if has_board {
println!("board-open: yes");
} else {
@ -292,7 +292,7 @@ async fn run() -> Result<(), KiCadError> {
}
}
Command::NetClasses => {
let classes = client.get_net_classes().await?;
let classes = client.get_net_classes()?;
println!("net_class_count={}", classes.len());
for class in classes {
println!(
@ -308,8 +308,8 @@ async fn run() -> Result<(), KiCadError> {
}
}
Command::SetNetClasses { merge_mode } => {
let classes = client.get_net_classes().await?;
let updated = client.set_net_classes(classes, merge_mode).await?;
let classes = client.get_net_classes()?;
let updated = client.set_net_classes(classes, merge_mode)?;
println!(
"net_class_count={} merge_mode={}",
updated.len(),
@ -317,7 +317,7 @@ async fn run() -> Result<(), KiCadError> {
);
}
Command::TextVariables => {
let variables = client.get_text_variables().await?;
let variables = client.get_text_variables()?;
println!("text_variable_count={}", variables.len());
for (name, value) in variables {
println!("name={} value={}", name, value);
@ -327,7 +327,7 @@ async fn run() -> Result<(), KiCadError> {
merge_mode,
variables,
} => {
let updated = client.set_text_variables(variables, merge_mode).await?;
let updated = client.set_text_variables(variables, merge_mode)?;
println!(
"text_variable_count={} merge_mode={}",
updated.len(),
@ -338,27 +338,25 @@ async fn run() -> Result<(), KiCadError> {
}
}
Command::ExpandTextVariables { text } => {
let expanded = client.expand_text_variables(text.clone()).await?;
let expanded = client.expand_text_variables(text.clone())?;
println!("expanded_count={}", expanded.len());
for (index, value) in expanded.iter().enumerate() {
println!("[{index}] input={} expanded={}", text[index], value);
}
}
Command::TextExtents { text } => {
let extents = client.get_text_extents(TextSpec::plain(text)).await?;
let extents = client.get_text_extents(TextSpec::plain(text))?;
println!(
"x_nm={} y_nm={} width_nm={} height_nm={}",
extents.x_nm, extents.y_nm, extents.width_nm, extents.height_nm
);
}
Command::TextAsShapes { text } => {
let entries = client
.get_text_as_shapes(
let entries = client.get_text_as_shapes(
text.into_iter()
.map(|value| TextObjectSpec::Text(TextSpec::plain(value)))
.collect(),
)
.await?;
)?;
println!("text_with_shapes_count={}", entries.len());
for (index, entry) in entries.iter().enumerate() {
let mut segment_count = 0;
@ -393,7 +391,7 @@ async fn run() -> Result<(), KiCadError> {
}
}
Command::Nets => {
let nets = client.get_nets().await?;
let nets = client.get_nets()?;
if nets.is_empty() {
println!("no nets returned");
} else {
@ -403,7 +401,7 @@ async fn run() -> Result<(), KiCadError> {
}
}
Command::EnabledLayers => {
let enabled = client.get_board_enabled_layers().await?;
let enabled = client.get_board_enabled_layers()?;
println!("copper_layer_count={}", enabled.copper_layer_count);
for layer in enabled.layers {
println!("layer_id={} layer_name={}", layer.id, layer.name);
@ -413,27 +411,25 @@ async fn run() -> Result<(), KiCadError> {
copper_layer_count,
layer_ids,
} => {
let enabled = client
.set_board_enabled_layers(copper_layer_count, layer_ids)
.await?;
let enabled = client.set_board_enabled_layers(copper_layer_count, layer_ids)?;
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?;
let layer = client.get_active_layer()?;
println!(
"active_layer_id={} active_layer_name={}",
layer.id, layer.name
);
}
Command::SetActiveLayer { layer_id } => {
client.set_active_layer(layer_id).await?;
client.set_active_layer(layer_id)?;
println!("set_active_layer_id={}", layer_id);
}
Command::VisibleLayers => {
let layers = client.get_visible_layers().await?;
let layers = client.get_visible_layers()?;
if layers.is_empty() {
println!("no visible layers returned");
} else {
@ -443,20 +439,18 @@ async fn run() -> Result<(), KiCadError> {
}
}
Command::SetVisibleLayers { layer_ids } => {
client.set_visible_layers(layer_ids.clone()).await?;
client.set_visible_layers(layer_ids.clone())?;
println!("set_visible_layer_count={}", layer_ids.len());
}
Command::BoardOrigin { kind } => {
let origin = client.get_board_origin(kind).await?;
let origin = client.get_board_origin(kind)?;
println!(
"origin_kind={} x_nm={} y_nm={}",
kind, origin.x_nm, origin.y_nm
);
}
Command::SetBoardOrigin { kind, x_nm, y_nm } => {
client
.set_board_origin(kind, Vector2Nm { x_nm, y_nm })
.await?;
client.set_board_origin(kind, Vector2Nm { x_nm, y_nm })?;
println!("set_origin_kind={} x_nm={} y_nm={}", kind, x_nm, y_nm);
}
Command::InjectDrcError {
@ -470,20 +464,18 @@ async fn run() -> Result<(), KiCadError> {
(Some(x_nm), Some(y_nm)) => Some(Vector2Nm { x_nm, y_nm }),
_ => None,
};
let marker = client
.inject_drc_error(severity, message, position, item_ids)
.await?;
let marker = client.inject_drc_error(severity, message, position, item_ids)?;
println!(
"drc_marker_id={}",
marker.unwrap_or_else(|| "-".to_string())
);
}
Command::RefreshEditor { frame } => {
client.refresh_editor(frame).await?;
client.refresh_editor(frame)?;
println!("refresh_editor=ok frame={}", frame);
}
Command::BeginCommit => {
let session = client.begin_commit().await?;
let session = client.begin_commit()?;
println!("commit_id={}", session.id);
}
Command::EndCommit {
@ -491,13 +483,11 @@ async fn run() -> Result<(), KiCadError> {
action,
message,
} => {
client
.end_commit(CommitSession { id }, action, message)
.await?;
client.end_commit(CommitSession { id }, action, message)?;
println!("end_commit=ok action={}", action);
}
Command::SaveDoc => {
client.save_document().await?;
client.save_document()?;
println!("save_document=ok");
}
Command::SaveCopy {
@ -505,27 +495,25 @@ async fn run() -> Result<(), KiCadError> {
overwrite,
include_project,
} => {
client
.save_copy_of_document(path, overwrite, include_project)
.await?;
client.save_copy_of_document(path, overwrite, include_project)?;
println!(
"save_copy_of_document=ok overwrite={} include_project={}",
overwrite, include_project
);
}
Command::RevertDoc => {
client.revert_document().await?;
client.revert_document()?;
println!("revert_document=ok");
}
Command::RunAction { action } => {
let status = client.run_action(action).await?;
let status = client.run_action(action)?;
println!("run_action_status={status:?}");
}
Command::CreateItems {
items,
container_id,
} => {
let created = client.create_items(items, container_id).await?;
let created = client.create_items(items, container_id)?;
println!("created_item_count={}", created.len());
for (index, item) in created.iter().enumerate() {
println!(
@ -536,7 +524,7 @@ async fn run() -> Result<(), KiCadError> {
}
}
Command::UpdateItems { items } => {
let updated = client.update_items(items).await?;
let updated = client.update_items(items)?;
println!("updated_item_count={}", updated.len());
for (index, item) in updated.iter().enumerate() {
println!(
@ -547,14 +535,14 @@ async fn run() -> Result<(), KiCadError> {
}
}
Command::DeleteItems { item_ids } => {
let deleted = client.delete_items(item_ids).await?;
let deleted = client.delete_items(item_ids)?;
println!("deleted_item_count={}", deleted.len());
for (index, item_id) in deleted.iter().enumerate() {
println!("[{index}] id={item_id}");
}
}
Command::ParseCreateItemsFromString { contents } => {
let created = client.parse_and_create_items_from_string(contents).await?;
let created = client.parse_and_create_items_from_string(contents)?;
println!("created_item_count={}", created.len());
for (index, item) in created.iter().enumerate() {
println!(
@ -565,32 +553,32 @@ async fn run() -> Result<(), KiCadError> {
}
}
Command::AddToSelection { item_ids } => {
let summary = client.add_to_selection(item_ids).await?;
let summary = client.add_to_selection(item_ids)?;
println!("selection_total={}", summary.total_items);
for entry in summary.type_url_counts {
println!("type_url={} count={}", entry.type_url, entry.count);
}
}
Command::RemoveFromSelection { item_ids } => {
let summary = client.remove_from_selection(item_ids).await?;
let summary = client.remove_from_selection(item_ids)?;
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?;
let summary = client.clear_selection()?;
println!("selection_total={}", summary.total_items);
}
Command::SelectionSummary => {
let summary = client.get_selection_summary().await?;
let summary = client.get_selection_summary()?;
println!("selection_total={}", summary.total_items);
for entry in summary.type_url_counts {
println!("type_url={} count={}", entry.type_url, entry.count);
}
}
Command::SelectionDetails => {
let details = client.get_selection_details().await?;
let details = client.get_selection_details()?;
println!("selection_total={}", details.len());
for (index, item) in details.iter().enumerate() {
println!(
@ -600,7 +588,7 @@ async fn run() -> Result<(), KiCadError> {
}
}
Command::SelectionRaw => {
let items = client.get_selection_raw().await?;
let items = client.get_selection_raw()?;
println!("selection_total={}", items.len());
for (index, item) in items.iter().enumerate() {
println!(
@ -612,7 +600,7 @@ async fn run() -> Result<(), KiCadError> {
}
}
Command::NetlistPads => {
let entries = client.get_pad_netlist().await?;
let entries = client.get_pad_netlist()?;
println!("pad_net_entries={}", entries.len());
for entry in entries {
println!(
@ -630,7 +618,7 @@ async fn run() -> Result<(), KiCadError> {
}
}
Command::ItemsById { item_ids } => {
let details = client.get_items_by_id_details(item_ids).await?;
let details = client.get_items_by_id_details(item_ids)?;
println!("items_total={}", details.len());
for (index, item) in details.iter().enumerate() {
println!(
@ -643,9 +631,7 @@ async fn run() -> Result<(), KiCadError> {
item_ids,
include_child_text,
} => {
let boxes = client
.get_item_bounding_boxes(item_ids, include_child_text)
.await?;
let boxes = client.get_item_bounding_boxes(item_ids, include_child_text)?;
println!("bbox_total={}", boxes.len());
for entry in boxes {
println!(
@ -660,13 +646,11 @@ async fn run() -> Result<(), KiCadError> {
y_nm,
tolerance_nm,
} => {
let result = client
.hit_test_item(item_id, Vector2Nm { x_nm, y_nm }, tolerance_nm)
.await?;
let result = client.hit_test_item(item_id, Vector2Nm { x_nm, y_nm }, tolerance_nm)?;
println!("hit_test={result}");
}
Command::PcbTypes => {
for entry in kicad_ipc::KiCadClient::pcb_object_type_codes() {
for entry in kicad_ipc_rs::KiCadClient::pcb_object_type_codes() {
println!("type_id={} type_name={}", entry.code, entry.name);
}
}
@ -674,9 +658,7 @@ async fn run() -> Result<(), KiCadError> {
type_codes,
include_debug,
} => {
let items = client
.get_items_raw_by_type_codes(type_codes.clone())
.await?;
let items = client.get_items_raw_by_type_codes(type_codes.clone())?;
println!(
"items_total={} requested_type_codes={:?}",
items.len(),
@ -684,7 +666,7 @@ async fn run() -> Result<(), KiCadError> {
);
for (index, item) in items.iter().enumerate() {
if include_debug {
let debug = kicad_ipc::KiCadClient::debug_any_item(item)?
let debug = kicad_ipc_rs::KiCadClient::debug_any_item(item)?
.replace('\n', "\\n")
.replace('\t', " ");
println!(
@ -705,11 +687,8 @@ async fn run() -> Result<(), KiCadError> {
}
}
Command::ItemsRawAllPcb { include_debug } => {
for object_type in kicad_ipc::KiCadClient::pcb_object_type_codes() {
match client
.get_items_raw_by_type_codes(vec![object_type.code])
.await
{
for object_type in kicad_ipc_rs::KiCadClient::pcb_object_type_codes() {
match client.get_items_raw_by_type_codes(vec![object_type.code]) {
Ok(items) => {
println!(
"type_id={} type_name={} item_count={}",
@ -719,7 +698,7 @@ async fn run() -> Result<(), KiCadError> {
);
for (index, item) in items.iter().enumerate() {
if include_debug {
let debug = kicad_ipc::KiCadClient::debug_any_item(item)?
let debug = kicad_ipc_rs::KiCadClient::debug_any_item(item)?
.replace('\n', "\\n")
.replace('\t', " ");
println!(
@ -753,9 +732,7 @@ async fn run() -> Result<(), KiCadError> {
layer_id,
include_debug,
} => {
let rows = client
.get_pad_shape_as_polygon(pad_ids.clone(), layer_id)
.await?;
let rows = client.get_pad_shape_as_polygon(pad_ids.clone(), layer_id)?;
println!(
"pad_shape_total={} layer_id={} requested_pad_count={}",
rows.len(),
@ -779,11 +756,9 @@ async fn run() -> Result<(), KiCadError> {
);
}
if include_debug {
let raw_chunks = client
.get_pad_shape_as_polygon_raw(pad_ids, layer_id)
.await?;
let raw_chunks = client.get_pad_shape_as_polygon_raw(pad_ids, layer_id)?;
for (chunk_index, chunk) in raw_chunks.iter().enumerate() {
let debug = kicad_ipc::KiCadClient::debug_any_item(chunk)?
let debug = kicad_ipc_rs::KiCadClient::debug_any_item(chunk)?
.replace('\n', "\\n")
.replace('\t', " ");
println!("raw_chunk={chunk_index} debug={debug}");
@ -795,9 +770,8 @@ async fn run() -> Result<(), KiCadError> {
layer_ids,
include_debug,
} => {
let rows = client
.check_padstack_presence_on_layers(item_ids.clone(), layer_ids.clone())
.await?;
let rows =
client.check_padstack_presence_on_layers(item_ids.clone(), layer_ids.clone())?;
println!(
"padstack_presence_total={} requested_item_count={} requested_layer_count={}",
rows.len(),
@ -811,11 +785,10 @@ async fn run() -> Result<(), KiCadError> {
);
}
if include_debug {
let raw_chunks = client
.check_padstack_presence_on_layers_raw(item_ids, layer_ids)
.await?;
let raw_chunks =
client.check_padstack_presence_on_layers_raw(item_ids, layer_ids)?;
for (chunk_index, chunk) in raw_chunks.iter().enumerate() {
let debug = kicad_ipc::KiCadClient::debug_any_item(chunk)?
let debug = kicad_ipc_rs::KiCadClient::debug_any_item(chunk)?
.replace('\n', "\\n")
.replace('\t', " ");
println!("raw_chunk={chunk_index} debug={debug}");
@ -823,7 +796,7 @@ async fn run() -> Result<(), KiCadError> {
}
}
Command::TitleBlock => {
let title_block = client.get_title_block_info().await?;
let title_block = client.get_title_block_info()?;
println!("title={}", title_block.title);
println!("date={}", title_block.date);
println!("revision={}", title_block.revision);
@ -833,28 +806,28 @@ async fn run() -> Result<(), KiCadError> {
}
}
Command::BoardAsString => {
let content = client.get_board_as_string().await?;
let content = client.get_board_as_string()?;
println!("{content}");
}
Command::SelectionAsString => {
let content = client.get_selection_as_string().await?;
let content = client.get_selection_as_string()?;
println!("{content}");
}
Command::Stackup => {
let stackup = client.get_board_stackup().await?;
let stackup = client.get_board_stackup()?;
println!("{stackup:#?}");
}
Command::UpdateStackup => {
let stackup = client.get_board_stackup().await?;
let updated = client.update_board_stackup(stackup).await?;
let stackup = client.get_board_stackup()?;
let updated = client.update_board_stackup(stackup)?;
println!("{updated:#?}");
}
Command::GraphicsDefaults => {
let defaults = client.get_graphics_defaults().await?;
let defaults = client.get_graphics_defaults()?;
println!("{defaults:#?}");
}
Command::Appearance => {
let appearance = client.get_board_editor_appearance_settings().await?;
let appearance = client.get_board_editor_appearance_settings()?;
println!("{appearance:#?}");
}
Command::SetAppearance {
@ -863,31 +836,31 @@ async fn run() -> Result<(), KiCadError> {
board_flip,
ratsnest_display,
} => {
let updated = client
.set_board_editor_appearance_settings(kicad_ipc::BoardEditorAppearanceSettings {
let updated = client.set_board_editor_appearance_settings(
kicad_ipc_rs::BoardEditorAppearanceSettings {
inactive_layer_display,
net_color_display,
board_flip,
ratsnest_display,
})
.await?;
},
)?;
println!("{updated:#?}");
}
Command::RefillZones { zone_ids } => {
client.refill_zones(zone_ids).await?;
client.refill_zones(zone_ids)?;
println!("refill_zones_dispatched=ok");
}
Command::InteractiveMoveItems { item_ids } => {
client.interactive_move_items(item_ids.clone()).await?;
client.interactive_move_items(item_ids.clone())?;
println!("interactive_move_item_count={}", item_ids.len());
}
Command::NetClass => {
let nets = client.get_nets().await?;
let netclasses = client.get_netclass_for_nets(nets).await?;
let nets = client.get_nets()?;
let netclasses = client.get_netclass_for_nets(nets)?;
println!("{netclasses:#?}");
}
Command::BoardReadReport { output } => {
let report = build_board_read_report_markdown(&client).await?;
let report = build_board_read_report_markdown(&client)?;
fs::write(&output, report).map_err(|err| KiCadError::Config {
reason: format!("failed to write report to `{}`: {err}", output.display()),
})?;
@ -897,9 +870,9 @@ async fn run() -> Result<(), KiCadError> {
print_proto_coverage_board_read();
}
Command::Smoke => {
client.ping().await?;
let version = client.get_version().await?;
let has_board = client.has_open_board().await?;
client.ping()?;
let version = client.get_version()?;
let has_board = client.has_open_board()?;
println!(
"smoke ok: version={}.{}.{} board_open={}",
version.major, version.minor, version.patch, has_board
@ -2189,13 +2162,13 @@ TYPES:
);
}
async fn build_board_read_report_markdown(client: &KiCadClient) -> Result<String, KiCadError> {
fn build_board_read_report_markdown(client: &KiCadClientBlocking) -> Result<String, KiCadError> {
let mut out = String::new();
out.push_str("# Board Read Reconstruction Report\n\n");
out.push_str("Generated by `kicad-ipc-cli board-read-report`.\n\n");
out.push_str("Goal: verify that non-mutating PCB API reads are sufficient to reconstruct board state.\n\n");
let version = client.get_version().await?;
let version = client.get_version()?;
out.push_str("## Session\n\n");
out.push_str(&format!(
"- KiCad version: {}.{}.{} ({})\n",
@ -2208,7 +2181,7 @@ async fn build_board_read_report_markdown(client: &KiCadClient) -> Result<String
));
out.push_str("## Open Documents\n\n");
let docs = client.get_open_documents(DocumentType::Pcb).await?;
let docs = client.get_open_documents(DocumentType::Pcb)?;
if docs.is_empty() {
out.push_str("- No open PCB docs\n\n");
} else {
@ -2230,7 +2203,7 @@ async fn build_board_read_report_markdown(client: &KiCadClient) -> Result<String
}
out.push_str("## Layer / Origin / Nets\n\n");
let enabled = client.get_board_enabled_layers().await?;
let enabled = client.get_board_enabled_layers()?;
let enabled_layers = enabled.layers.clone();
out.push_str(&format!(
"- copper_layer_count: {}\n",
@ -2241,34 +2214,30 @@ async fn build_board_read_report_markdown(client: &KiCadClient) -> Result<String
out.push_str(&format!(" - {} ({})\n", layer.name, layer.id));
}
let visible_layers = client.get_visible_layers().await?;
let visible_layers = client.get_visible_layers()?;
out.push_str("- visible_layers:\n");
for layer in visible_layers {
out.push_str(&format!(" - {} ({})\n", layer.name, layer.id));
}
let active_layer = client.get_active_layer().await?;
let active_layer = client.get_active_layer()?;
out.push_str(&format!(
"- active_layer: {} ({})\n",
active_layer.name, active_layer.id
));
let grid_origin = client
.get_board_origin(kicad_ipc::BoardOriginKind::Grid)
.await?;
let grid_origin = client.get_board_origin(kicad_ipc_rs::BoardOriginKind::Grid)?;
out.push_str(&format!(
"- grid_origin_nm: {},{}\n",
grid_origin.x_nm, grid_origin.y_nm
));
let drill_origin = client
.get_board_origin(kicad_ipc::BoardOriginKind::Drill)
.await?;
let drill_origin = client.get_board_origin(kicad_ipc_rs::BoardOriginKind::Drill)?;
out.push_str(&format!(
"- drill_origin_nm: {},{}\n",
drill_origin.x_nm, drill_origin.y_nm
));
let nets = client.get_nets().await?;
let nets = client.get_nets()?;
out.push_str(&format!("- net_count: {}\n", nets.len()));
out.push_str("\n### Netlist\n\n");
for net in &nets {
@ -2277,7 +2246,7 @@ async fn build_board_read_report_markdown(client: &KiCadClient) -> Result<String
out.push('\n');
out.push_str("### Pad-Level Netlist (Footprint/Pad/Net)\n\n");
let pad_entries = client.get_pad_netlist().await?;
let pad_entries = client.get_pad_netlist()?;
let mut pad_ids = BTreeSet::new();
out.push_str(&format!("- pad_entry_count: {}\n", pad_entries.len()));
for (index, entry) in pad_entries.iter().enumerate() {
@ -2319,9 +2288,8 @@ async fn build_board_read_report_markdown(client: &KiCadClient) -> Result<String
));
let mut present_pad_ids_by_layer: BTreeMap<i32, BTreeSet<String>> = BTreeMap::new();
let presence_rows = client
.check_padstack_presence_on_layers(pad_ids.clone(), enabled_layer_ids)
.await?;
let presence_rows =
client.check_padstack_presence_on_layers(pad_ids.clone(), enabled_layer_ids)?;
out.push_str(&format!(
"- presence_entry_count: {}\n",
presence_rows.len()
@ -2372,9 +2340,7 @@ async fn build_board_read_report_markdown(client: &KiCadClient) -> Result<String
continue;
}
let polygons = client
.get_pad_shape_as_polygon(pad_ids_on_layer, layer.id)
.await?;
let polygons = client.get_pad_shape_as_polygon(pad_ids_on_layer, layer.id)?;
out.push_str(&format!("- polygon_entry_count: {}\n\n", polygons.len()));
for row in polygons {
let summary = polygon_geometry_summary(&row.polygon);
@ -2395,7 +2361,7 @@ async fn build_board_read_report_markdown(client: &KiCadClient) -> Result<String
out.push_str("## Board/Editor Structures\n\n");
out.push_str("### Title Block\n\n");
let title_block = client.get_title_block_info().await?;
let title_block = client.get_title_block_info()?;
out.push_str(&format!("- title: {}\n", title_block.title));
out.push_str(&format!("- date: {}\n", title_block.date));
out.push_str(&format!("- revision: {}\n", title_block.revision));
@ -2406,40 +2372,35 @@ async fn build_board_read_report_markdown(client: &KiCadClient) -> Result<String
out.push('\n');
out.push_str("### Stackup\n\n```text\n");
out.push_str(&format!("{:#?}", client.get_board_stackup().await?));
out.push_str(&format!("{:#?}", client.get_board_stackup()?));
out.push_str("\n```\n\n");
out.push_str("### Graphics Defaults\n\n```text\n");
out.push_str(&format!("{:#?}", client.get_graphics_defaults().await?));
out.push_str(&format!("{:#?}", client.get_graphics_defaults()?));
out.push_str("\n```\n\n");
out.push_str("### Editor Appearance\n\n```text\n");
out.push_str(&format!(
"{:#?}",
client.get_board_editor_appearance_settings().await?
client.get_board_editor_appearance_settings()?
));
out.push_str("\n```\n\n");
out.push_str("### NetClass Map\n\n```text\n");
out.push_str(&format!(
"{:#?}",
client
.get_netclass_for_nets(client.get_nets().await?)
.await?
client.get_netclass_for_nets(client.get_nets()?)?
));
out.push_str("\n```\n\n");
out.push_str("## PCB Item Coverage (All KOT_PCB_* Types)\n\n");
let mut missing_types: Vec<PcbObjectTypeCode> = Vec::new();
for object_type in kicad_ipc::KiCadClient::pcb_object_type_codes() {
for object_type in kicad_ipc_rs::KiCadClient::pcb_object_type_codes() {
out.push_str(&format!(
"### {} ({})\n\n",
object_type.name, object_type.code
));
match client
.get_items_raw_by_type_codes(vec![object_type.code])
.await
{
match client.get_items_raw_by_type_codes(vec![object_type.code]) {
Ok(items) => {
if items.is_empty() {
missing_types.push(*object_type);
@ -2451,7 +2412,7 @@ async fn build_board_read_report_markdown(client: &KiCadClient) -> Result<String
.take(REPORT_MAX_ITEM_DEBUG_ROWS_PER_TYPE)
.enumerate()
{
let mut debug = kicad_ipc::KiCadClient::debug_any_item(item)?;
let mut debug = kicad_ipc_rs::KiCadClient::debug_any_item(item)?;
if debug.len() > REPORT_MAX_ITEM_DEBUG_CHARS {
debug.truncate(REPORT_MAX_ITEM_DEBUG_CHARS);
debug.push_str("\n...<truncated; use items-raw CLI for full payload>");
@ -2495,7 +2456,7 @@ async fn build_board_read_report_markdown(client: &KiCadClient) -> Result<String
}
out.push_str("## Board File Snapshot (Raw)\n\n```scheme\n");
let mut board_text = client.get_board_as_string().await?;
let mut board_text = client.get_board_as_string()?;
if board_text.len() > REPORT_MAX_BOARD_SNAPSHOT_CHARS {
board_text.truncate(REPORT_MAX_BOARD_SNAPSHOT_CHARS);
board_text.push_str(
@ -2665,7 +2626,7 @@ struct PolygonGeometrySummary {
arc_nodes: usize,
}
fn polygon_geometry_summary(polygon: &kicad_ipc::PolygonWithHolesNm) -> PolygonGeometrySummary {
fn polygon_geometry_summary(polygon: &kicad_ipc_rs::PolygonWithHolesNm) -> PolygonGeometrySummary {
let mut summary = PolygonGeometrySummary {
hole_count: polygon.holes.len(),
..PolygonGeometrySummary::default()
@ -2675,8 +2636,8 @@ fn polygon_geometry_summary(polygon: &kicad_ipc::PolygonWithHolesNm) -> PolygonG
summary.outline_nodes = outline.nodes.len();
for node in &outline.nodes {
match node {
kicad_ipc::PolyLineNodeGeometryNm::Point(_) => summary.point_nodes += 1,
kicad_ipc::PolyLineNodeGeometryNm::Arc(_) => summary.arc_nodes += 1,
kicad_ipc_rs::PolyLineNodeGeometryNm::Point(_) => summary.point_nodes += 1,
kicad_ipc_rs::PolyLineNodeGeometryNm::Arc(_) => summary.arc_nodes += 1,
}
}
}
@ -2685,8 +2646,8 @@ fn polygon_geometry_summary(polygon: &kicad_ipc::PolygonWithHolesNm) -> PolygonG
summary.hole_nodes_total += hole.nodes.len();
for node in &hole.nodes {
match node {
kicad_ipc::PolyLineNodeGeometryNm::Point(_) => summary.point_nodes += 1,
kicad_ipc::PolyLineNodeGeometryNm::Arc(_) => summary.arc_nodes += 1,
kicad_ipc_rs::PolyLineNodeGeometryNm::Point(_) => summary.point_nodes += 1,
kicad_ipc_rs::PolyLineNodeGeometryNm::Arc(_) => summary.arc_nodes += 1,
}
}
}
@ -2765,7 +2726,7 @@ fn hex_nibble(c: char) -> Result<u8, String> {
#[cfg(test)]
mod tests {
use super::{parse_args_from, Command};
use kicad_ipc::{
use kicad_ipc_rs::{
BoardFlipMode, BoardOriginKind, CommitAction, DrcSeverity, InactiveLayerDisplayMode,
NetColorDisplayMode, RatsnestDisplayMode,
};
@ -2930,7 +2891,7 @@ mod tests {
match command {
Command::SetNetClasses { merge_mode } => {
assert_eq!(merge_mode, kicad_ipc::MapMergeMode::Replace)
assert_eq!(merge_mode, kicad_ipc_rs::MapMergeMode::Replace)
}
other => panic!("unexpected command variant: {other:?}"),
}
@ -2952,7 +2913,7 @@ mod tests {
merge_mode,
variables,
} => {
assert_eq!(merge_mode, kicad_ipc::MapMergeMode::Replace);
assert_eq!(merge_mode, kicad_ipc_rs::MapMergeMode::Replace);
assert_eq!(variables.get("REV").map(|value| value.as_str()), Some("A"));
}
other => panic!("unexpected command variant: {other:?}"),