feat(blocking): ship full sync wrapper parity
This commit is contained in:
parent
81d98bbfc4
commit
a0271419cc
|
|
@ -11,6 +11,8 @@ keywords = ["kicad", "eda", "pcb", "ipc"]
|
||||||
categories = ["api-bindings", "asynchronous"]
|
categories = ["api-bindings", "asynchronous"]
|
||||||
include = [
|
include = [
|
||||||
"/src/**",
|
"/src/**",
|
||||||
|
"/test-scripts/kicad-ipc-cli.rs",
|
||||||
|
"/docs/TEST_CLI.md",
|
||||||
"/README.md",
|
"/README.md",
|
||||||
"/LICENSE",
|
"/LICENSE",
|
||||||
"/Cargo.toml",
|
"/Cargo.toml",
|
||||||
|
|
@ -22,6 +24,11 @@ async = ["dep:nng", "dep:prost", "dep:prost-types", "dep:tokio"]
|
||||||
blocking = ["async"]
|
blocking = ["async"]
|
||||||
tracing = ["dep:tracing"]
|
tracing = ["dep:tracing"]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "kicad-ipc-cli"
|
||||||
|
path = "test-scripts/kicad-ipc-cli.rs"
|
||||||
|
required-features = ["blocking"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nng = { version = "1.0.1", optional = true }
|
nng = { version = "1.0.1", optional = true }
|
||||||
prost = { version = "0.14.3", optional = true }
|
prost = { version = "0.14.3", optional = true }
|
||||||
|
|
|
||||||
63
README.md
63
README.md
|
|
@ -8,13 +8,66 @@ Maintainer workflow: see `CONTRIBUTIONS.md`.
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
Alpha. `v0.1.0` release candidate.
|
Alpha. `v0.1.1` released.
|
||||||
|
|
||||||
- Async API: implemented and usable.
|
- Async API (default): implemented and usable.
|
||||||
- Sync/blocking wrapper API: planned, not shipped yet.
|
- Sync/blocking wrapper API (`feature = "blocking"`): implemented with full async parity.
|
||||||
- Real-world user testing: still limited.
|
- Real-world user testing: still limited.
|
||||||
- Issues and PRs welcome.
|
- 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
|
## Protobuf Source
|
||||||
|
|
||||||
This crate ships checked-in Rust protobuf output under `src/proto/generated/`.
|
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
|
## 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
|
## Runtime Compatibility Notes
|
||||||
|
|
||||||
|
|
@ -169,7 +223,6 @@ Legend:
|
||||||
## Roadmap
|
## Roadmap
|
||||||
|
|
||||||
`v0.2.0` target:
|
`v0.2.0` target:
|
||||||
- Add full sync/blocking wrapper API parity over async client.
|
|
||||||
- Expand runtime + integration testing coverage.
|
- Expand runtime + integration testing coverage.
|
||||||
- Set up CI to run checks/tests on commits and PRs.
|
- Set up CI to run checks/tests on commits and PRs.
|
||||||
- Continue API hardening/docs/examples for stable `1.0` path.
|
- Continue API hardening/docs/examples for stable `1.0` path.
|
||||||
|
|
|
||||||
144
docs/TEST_CLI.md
144
docs/TEST_CLI.md
|
|
@ -6,9 +6,11 @@ CLI binary path:
|
||||||
Run help:
|
Run help:
|
||||||
|
|
||||||
```bash
|
```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
|
## Prereqs
|
||||||
|
|
||||||
1. KiCad running.
|
1. KiCad running.
|
||||||
|
|
@ -20,145 +22,145 @@ cargo run --bin kicad-ipc-cli -- help
|
||||||
Ping:
|
Ping:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo run --bin kicad-ipc-cli -- ping
|
cargo run --features blocking --bin kicad-ipc-cli -- ping
|
||||||
```
|
```
|
||||||
|
|
||||||
Version:
|
Version:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo run --bin kicad-ipc-cli -- version
|
cargo run --features blocking --bin kicad-ipc-cli -- version
|
||||||
```
|
```
|
||||||
|
|
||||||
Resolve KiCad binary path (default `kicad-cli`):
|
Resolve KiCad binary path (default `kicad-cli`):
|
||||||
|
|
||||||
```bash
|
```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`):
|
Resolve plugin settings path (default identifier `kicad-ipc-rust`):
|
||||||
|
|
||||||
```bash
|
```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:
|
List open PCB docs:
|
||||||
|
|
||||||
```bash
|
```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:
|
Check board open:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo run --bin kicad-ipc-cli -- board-open
|
cargo run --features blocking --bin kicad-ipc-cli -- board-open
|
||||||
```
|
```
|
||||||
|
|
||||||
List nets:
|
List nets:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo run --bin kicad-ipc-cli -- nets
|
cargo run --features blocking --bin kicad-ipc-cli -- nets
|
||||||
```
|
```
|
||||||
|
|
||||||
List project net classes:
|
List project net classes:
|
||||||
|
|
||||||
```bash
|
```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:
|
Write current net classes back with selected merge mode:
|
||||||
|
|
||||||
```bash
|
```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:
|
List text variables for current board document:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo run --bin kicad-ipc-cli -- text-variables
|
cargo run --features blocking --bin kicad-ipc-cli -- text-variables
|
||||||
```
|
```
|
||||||
|
|
||||||
Set text variables:
|
Set text variables:
|
||||||
|
|
||||||
```bash
|
```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:
|
Expand text variables in one or more input strings:
|
||||||
|
|
||||||
```bash
|
```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:
|
Measure text extents:
|
||||||
|
|
||||||
```bash
|
```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:
|
Convert text to shape primitives:
|
||||||
|
|
||||||
```bash
|
```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:
|
List enabled board layers:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo run --bin kicad-ipc-cli -- enabled-layers
|
cargo run --features blocking --bin kicad-ipc-cli -- enabled-layers
|
||||||
```
|
```
|
||||||
|
|
||||||
Set enabled board layers:
|
Set enabled board layers:
|
||||||
|
|
||||||
```bash
|
```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:
|
Show active layer:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo run --bin kicad-ipc-cli -- active-layer
|
cargo run --features blocking --bin kicad-ipc-cli -- active-layer
|
||||||
```
|
```
|
||||||
|
|
||||||
Set active layer:
|
Set active layer:
|
||||||
|
|
||||||
```bash
|
```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:
|
Show visible layers:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo run --bin kicad-ipc-cli -- visible-layers
|
cargo run --features blocking --bin kicad-ipc-cli -- visible-layers
|
||||||
```
|
```
|
||||||
|
|
||||||
Set visible layers:
|
Set visible layers:
|
||||||
|
|
||||||
```bash
|
```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):
|
Show board origin (grid origin by default):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo run --bin kicad-ipc-cli -- board-origin
|
cargo run --features blocking --bin kicad-ipc-cli -- board-origin
|
||||||
```
|
```
|
||||||
|
|
||||||
Show drill origin:
|
Show drill origin:
|
||||||
|
|
||||||
```bash
|
```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:
|
Set board origin:
|
||||||
|
|
||||||
```bash
|
```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:
|
Refresh PCB editor:
|
||||||
|
|
||||||
```bash
|
```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`.
|
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:
|
Start a staged commit and print commit ID:
|
||||||
|
|
||||||
```bash
|
```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:
|
End a staged commit:
|
||||||
|
|
||||||
```bash
|
```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:
|
Save current board document:
|
||||||
|
|
||||||
```bash
|
```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:
|
Save a copy of current board document:
|
||||||
|
|
||||||
```bash
|
```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:
|
Revert current board document from disk:
|
||||||
|
|
||||||
```bash
|
```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:
|
Run a raw KiCad tool action:
|
||||||
|
|
||||||
```bash
|
```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):
|
Create raw Any item payload(s):
|
||||||
|
|
||||||
```bash
|
```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):
|
Update raw Any item payload(s):
|
||||||
|
|
||||||
```bash
|
```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:
|
Delete items by ID:
|
||||||
|
|
||||||
```bash
|
```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:
|
Parse and create items from s-expression:
|
||||||
|
|
||||||
```bash
|
```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:
|
Show summary of current PCB selection by item type:
|
||||||
|
|
||||||
```bash
|
```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:
|
Show parsed details for currently selected items:
|
||||||
|
|
||||||
```bash
|
```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:
|
Show raw protobuf payload bytes for selected items:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo run --bin kicad-ipc-cli -- selection-raw
|
cargo run --features blocking --bin kicad-ipc-cli -- selection-raw
|
||||||
```
|
```
|
||||||
|
|
||||||
Add items to current selection:
|
Add items to current selection:
|
||||||
|
|
||||||
```bash
|
```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:
|
Remove items from current selection:
|
||||||
|
|
||||||
```bash
|
```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:
|
Clear current selection:
|
||||||
|
|
||||||
```bash
|
```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):
|
Show pad-level netlist entries (footprint/pad/net):
|
||||||
|
|
||||||
```bash
|
```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:
|
Show parsed details for specific item IDs:
|
||||||
|
|
||||||
```bash
|
```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:
|
Show item bounding boxes:
|
||||||
|
|
||||||
```bash
|
```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):
|
Include child text in the bounding box (for items such as footprints):
|
||||||
|
|
||||||
```bash
|
```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:
|
Run hit-test on a specific item:
|
||||||
|
|
||||||
```bash
|
```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:
|
List all PCB object type IDs from the proto enum:
|
||||||
|
|
||||||
```bash
|
```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:
|
Dump raw item payloads for one or more PCB object type IDs:
|
||||||
|
|
||||||
```bash
|
```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:
|
Dump raw payloads for all PCB object classes:
|
||||||
|
|
||||||
```bash
|
```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:
|
Check whether pads/vias have flashed padstack shapes on specific layers:
|
||||||
|
|
||||||
```bash
|
```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:
|
Get polygonized pad shape(s) on a specific layer:
|
||||||
|
|
||||||
```bash
|
```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):
|
Dump board text (KiCad s-expression):
|
||||||
|
|
||||||
```bash
|
```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):
|
Dump selection text (KiCad s-expression):
|
||||||
|
|
||||||
```bash
|
```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:
|
Dump title block fields:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo run --bin kicad-ipc-cli -- title-block
|
cargo run --features blocking --bin kicad-ipc-cli -- title-block
|
||||||
```
|
```
|
||||||
|
|
||||||
Show typed stackup/graphics/appearance:
|
Show typed stackup/graphics/appearance:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo run --bin kicad-ipc-cli -- stackup
|
cargo run --features blocking --bin kicad-ipc-cli -- stackup
|
||||||
cargo run --bin kicad-ipc-cli -- update-stackup
|
cargo run --features blocking --bin kicad-ipc-cli -- update-stackup
|
||||||
cargo run --bin kicad-ipc-cli -- graphics-defaults
|
cargo run --features blocking --bin kicad-ipc-cli -- graphics-defaults
|
||||||
cargo run --bin kicad-ipc-cli -- appearance
|
cargo run --features blocking --bin kicad-ipc-cli -- appearance
|
||||||
```
|
```
|
||||||
|
|
||||||
Set editor appearance:
|
Set editor appearance:
|
||||||
|
|
||||||
```bash
|
```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:
|
Inject DRC marker:
|
||||||
|
|
||||||
```bash
|
```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:
|
Refill all zones:
|
||||||
|
|
||||||
```bash
|
```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:
|
Start interactive move tool for one or more item IDs:
|
||||||
|
|
||||||
```bash
|
```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:
|
Show typed netclass map:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo run --bin kicad-ipc-cli -- netclass
|
cargo run --features blocking --bin kicad-ipc-cli -- netclass
|
||||||
```
|
```
|
||||||
|
|
||||||
Print proto command coverage status (board read):
|
Print proto command coverage status (board read):
|
||||||
|
|
||||||
```bash
|
```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:
|
Generate full board-read reconstruction markdown report:
|
||||||
|
|
||||||
```bash
|
```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:
|
Notes:
|
||||||
|
|
@ -395,13 +397,13 @@ Notes:
|
||||||
Get current project path (derived from open PCB docs):
|
Get current project path (derived from open PCB docs):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo run --bin kicad-ipc-cli -- project-path
|
cargo run --features blocking --bin kicad-ipc-cli -- project-path
|
||||||
```
|
```
|
||||||
|
|
||||||
Smoke check:
|
Smoke check:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo run --bin kicad-ipc-cli -- smoke
|
cargo run --features blocking --bin kicad-ipc-cli -- smoke
|
||||||
```
|
```
|
||||||
|
|
||||||
## Common Flags
|
## Common Flags
|
||||||
|
|
@ -409,25 +411,25 @@ cargo run --bin kicad-ipc-cli -- smoke
|
||||||
Custom socket:
|
Custom socket:
|
||||||
|
|
||||||
```bash
|
```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:
|
Custom token:
|
||||||
|
|
||||||
```bash
|
```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):
|
Stable client name (needed when pairing `begin-commit` and `end-commit` across separate CLI runs):
|
||||||
|
|
||||||
```bash
|
```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:
|
Custom timeout:
|
||||||
|
|
||||||
```bash
|
```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
|
## Failure Hints
|
||||||
|
|
|
||||||
656
src/blocking.rs
656
src/blocking.rs
|
|
@ -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::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)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct KiCadClientBlocking {
|
pub struct KiCadClientBlocking {
|
||||||
inner: KiCadClient,
|
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 {
|
impl KiCadClientBlocking {
|
||||||
|
pub fn builder() -> KiCadClientBlockingBuilder {
|
||||||
|
KiCadClientBlockingBuilder::new()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn connect() -> Result<Self, KiCadError> {
|
pub fn connect() -> Result<Self, KiCadError> {
|
||||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
KiCadClientBlockingBuilder::new().connect()
|
||||||
.enable_time()
|
}
|
||||||
.build()
|
|
||||||
.map_err(|err| KiCadError::RuntimeJoin(err.to_string()))?;
|
pub fn timeout(&self) -> Duration {
|
||||||
let inner = runtime.block_on(KiCadClient::connect())?;
|
self.inner.timeout()
|
||||||
Ok(Self { inner })
|
}
|
||||||
|
|
||||||
|
pub fn socket_uri(&self) -> &str {
|
||||||
|
self.inner.socket_uri()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn inner(&self) -> &KiCadClient {
|
pub fn inner(&self) -> &KiCadClient {
|
||||||
&self.inner
|
&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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,9 @@ pub enum KiCadError {
|
||||||
#[error("runtime task join failed: {0}")]
|
#[error("runtime task join failed: {0}")]
|
||||||
RuntimeJoin(String),
|
RuntimeJoin(String),
|
||||||
|
|
||||||
|
#[error("blocking runtime is unavailable")]
|
||||||
|
BlockingRuntimeClosed,
|
||||||
|
|
||||||
#[error("mutex poisoned")]
|
#[error("mutex poisoned")]
|
||||||
InternalPoisoned,
|
InternalPoisoned,
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ pub mod blocking;
|
||||||
|
|
||||||
pub(crate) mod proto;
|
pub(crate) mod proto;
|
||||||
|
|
||||||
|
#[cfg(feature = "blocking")]
|
||||||
|
pub use crate::blocking::{KiCadClientBlocking, KiCadClientBlockingBuilder};
|
||||||
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::kicad_api_version::KICAD_API_VERSION;
|
pub use crate::kicad_api_version::KICAD_API_VERSION;
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,9 @@ use std::process::ExitCode;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use kicad_ipc::{
|
use kicad_ipc_rs::{
|
||||||
BoardFlipMode, BoardOriginKind, ClientBuilder, CommitAction, CommitSession, DocumentType,
|
BoardFlipMode, BoardOriginKind, CommitAction, CommitSession, DocumentType, DrcSeverity,
|
||||||
DrcSeverity, EditorFrameType, InactiveLayerDisplayMode, KiCadClient, KiCadError, MapMergeMode,
|
EditorFrameType, InactiveLayerDisplayMode, KiCadClientBlocking, KiCadError, MapMergeMode,
|
||||||
NetColorDisplayMode, PadstackPresenceState, PcbObjectTypeCode, RatsnestDisplayMode,
|
NetColorDisplayMode, PadstackPresenceState, PcbObjectTypeCode, RatsnestDisplayMode,
|
||||||
TextObjectSpec, TextShapeGeometry, TextSpec, Vector2Nm,
|
TextObjectSpec, TextShapeGeometry, TextSpec, Vector2Nm,
|
||||||
};
|
};
|
||||||
|
|
@ -190,9 +190,8 @@ enum Command {
|
||||||
Help,
|
Help,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main(flavor = "current_thread")]
|
fn main() -> ExitCode {
|
||||||
async fn main() -> ExitCode {
|
match run() {
|
||||||
match run().await {
|
|
||||||
Ok(()) => ExitCode::SUCCESS,
|
Ok(()) => ExitCode::SUCCESS,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
eprintln!("error: {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()?;
|
let (config, command) = parse_args()?;
|
||||||
|
|
||||||
if matches!(command, Command::Help) {
|
if matches!(command, Command::Help) {
|
||||||
|
|
@ -224,7 +223,8 @@ async fn run() -> Result<(), KiCadError> {
|
||||||
return Ok(());
|
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 {
|
if let Some(socket) = config.socket {
|
||||||
builder = builder.socket_path(socket);
|
builder = builder.socket_path(socket);
|
||||||
}
|
}
|
||||||
|
|
@ -235,30 +235,30 @@ async fn run() -> Result<(), KiCadError> {
|
||||||
builder = builder.client_name(client_name);
|
builder = builder.client_name(client_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
let client = builder.connect().await?;
|
let client = builder.connect()?;
|
||||||
|
|
||||||
match command {
|
match command {
|
||||||
Command::Ping => {
|
Command::Ping => {
|
||||||
client.ping().await?;
|
client.ping()?;
|
||||||
println!("pong");
|
println!("pong");
|
||||||
}
|
}
|
||||||
Command::Version => {
|
Command::Version => {
|
||||||
let version = client.get_version().await?;
|
let version = client.get_version()?;
|
||||||
println!(
|
println!(
|
||||||
"version: {}.{}.{} ({})",
|
"version: {}.{}.{} ({})",
|
||||||
version.major, version.minor, version.patch, version.full_version
|
version.major, version.minor, version.patch, version.full_version
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Command::KiCadBinaryPath { binary_name } => {
|
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}");
|
println!("kicad_binary_path={path}");
|
||||||
}
|
}
|
||||||
Command::PluginSettingsPath { identifier } => {
|
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}");
|
println!("plugin_settings_path={path}");
|
||||||
}
|
}
|
||||||
Command::OpenDocs { document_type } => {
|
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() {
|
if docs.is_empty() {
|
||||||
println!("no open `{document_type}` documents");
|
println!("no open `{document_type}` documents");
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -280,11 +280,11 @@ async fn run() -> Result<(), KiCadError> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command::ProjectPath => {
|
Command::ProjectPath => {
|
||||||
let path = client.get_current_project_path().await?;
|
let path = client.get_current_project_path()?;
|
||||||
println!("project_path={}", path.display());
|
println!("project_path={}", path.display());
|
||||||
}
|
}
|
||||||
Command::BoardOpen => {
|
Command::BoardOpen => {
|
||||||
let has_board = client.has_open_board().await?;
|
let has_board = client.has_open_board()?;
|
||||||
if has_board {
|
if has_board {
|
||||||
println!("board-open: yes");
|
println!("board-open: yes");
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -292,7 +292,7 @@ async fn run() -> Result<(), KiCadError> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command::NetClasses => {
|
Command::NetClasses => {
|
||||||
let classes = client.get_net_classes().await?;
|
let classes = client.get_net_classes()?;
|
||||||
println!("net_class_count={}", classes.len());
|
println!("net_class_count={}", classes.len());
|
||||||
for class in classes {
|
for class in classes {
|
||||||
println!(
|
println!(
|
||||||
|
|
@ -308,8 +308,8 @@ async fn run() -> Result<(), KiCadError> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command::SetNetClasses { merge_mode } => {
|
Command::SetNetClasses { merge_mode } => {
|
||||||
let classes = client.get_net_classes().await?;
|
let classes = client.get_net_classes()?;
|
||||||
let updated = client.set_net_classes(classes, merge_mode).await?;
|
let updated = client.set_net_classes(classes, merge_mode)?;
|
||||||
println!(
|
println!(
|
||||||
"net_class_count={} merge_mode={}",
|
"net_class_count={} merge_mode={}",
|
||||||
updated.len(),
|
updated.len(),
|
||||||
|
|
@ -317,7 +317,7 @@ async fn run() -> Result<(), KiCadError> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Command::TextVariables => {
|
Command::TextVariables => {
|
||||||
let variables = client.get_text_variables().await?;
|
let variables = client.get_text_variables()?;
|
||||||
println!("text_variable_count={}", variables.len());
|
println!("text_variable_count={}", variables.len());
|
||||||
for (name, value) in variables {
|
for (name, value) in variables {
|
||||||
println!("name={} value={}", name, value);
|
println!("name={} value={}", name, value);
|
||||||
|
|
@ -327,7 +327,7 @@ async fn run() -> Result<(), KiCadError> {
|
||||||
merge_mode,
|
merge_mode,
|
||||||
variables,
|
variables,
|
||||||
} => {
|
} => {
|
||||||
let updated = client.set_text_variables(variables, merge_mode).await?;
|
let updated = client.set_text_variables(variables, merge_mode)?;
|
||||||
println!(
|
println!(
|
||||||
"text_variable_count={} merge_mode={}",
|
"text_variable_count={} merge_mode={}",
|
||||||
updated.len(),
|
updated.len(),
|
||||||
|
|
@ -338,27 +338,25 @@ async fn run() -> Result<(), KiCadError> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command::ExpandTextVariables { text } => {
|
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());
|
println!("expanded_count={}", expanded.len());
|
||||||
for (index, value) in expanded.iter().enumerate() {
|
for (index, value) in expanded.iter().enumerate() {
|
||||||
println!("[{index}] input={} expanded={}", text[index], value);
|
println!("[{index}] input={} expanded={}", text[index], value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command::TextExtents { text } => {
|
Command::TextExtents { text } => {
|
||||||
let extents = client.get_text_extents(TextSpec::plain(text)).await?;
|
let extents = client.get_text_extents(TextSpec::plain(text))?;
|
||||||
println!(
|
println!(
|
||||||
"x_nm={} y_nm={} width_nm={} height_nm={}",
|
"x_nm={} y_nm={} width_nm={} height_nm={}",
|
||||||
extents.x_nm, extents.y_nm, extents.width_nm, extents.height_nm
|
extents.x_nm, extents.y_nm, extents.width_nm, extents.height_nm
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Command::TextAsShapes { text } => {
|
Command::TextAsShapes { text } => {
|
||||||
let entries = client
|
let entries = client.get_text_as_shapes(
|
||||||
.get_text_as_shapes(
|
|
||||||
text.into_iter()
|
text.into_iter()
|
||||||
.map(|value| TextObjectSpec::Text(TextSpec::plain(value)))
|
.map(|value| TextObjectSpec::Text(TextSpec::plain(value)))
|
||||||
.collect(),
|
.collect(),
|
||||||
)
|
)?;
|
||||||
.await?;
|
|
||||||
println!("text_with_shapes_count={}", entries.len());
|
println!("text_with_shapes_count={}", entries.len());
|
||||||
for (index, entry) in entries.iter().enumerate() {
|
for (index, entry) in entries.iter().enumerate() {
|
||||||
let mut segment_count = 0;
|
let mut segment_count = 0;
|
||||||
|
|
@ -393,7 +391,7 @@ async fn run() -> Result<(), KiCadError> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command::Nets => {
|
Command::Nets => {
|
||||||
let nets = client.get_nets().await?;
|
let nets = client.get_nets()?;
|
||||||
if nets.is_empty() {
|
if nets.is_empty() {
|
||||||
println!("no nets returned");
|
println!("no nets returned");
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -403,7 +401,7 @@ async fn run() -> Result<(), KiCadError> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command::EnabledLayers => {
|
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);
|
println!("copper_layer_count={}", enabled.copper_layer_count);
|
||||||
for layer in enabled.layers {
|
for layer in enabled.layers {
|
||||||
println!("layer_id={} layer_name={}", layer.id, layer.name);
|
println!("layer_id={} layer_name={}", layer.id, layer.name);
|
||||||
|
|
@ -413,27 +411,25 @@ async fn run() -> Result<(), KiCadError> {
|
||||||
copper_layer_count,
|
copper_layer_count,
|
||||||
layer_ids,
|
layer_ids,
|
||||||
} => {
|
} => {
|
||||||
let enabled = client
|
let enabled = client.set_board_enabled_layers(copper_layer_count, layer_ids)?;
|
||||||
.set_board_enabled_layers(copper_layer_count, layer_ids)
|
|
||||||
.await?;
|
|
||||||
println!("copper_layer_count={}", enabled.copper_layer_count);
|
println!("copper_layer_count={}", enabled.copper_layer_count);
|
||||||
for layer in enabled.layers {
|
for layer in enabled.layers {
|
||||||
println!("layer_id={} layer_name={}", layer.id, layer.name);
|
println!("layer_id={} layer_name={}", layer.id, layer.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command::ActiveLayer => {
|
Command::ActiveLayer => {
|
||||||
let layer = client.get_active_layer().await?;
|
let layer = client.get_active_layer()?;
|
||||||
println!(
|
println!(
|
||||||
"active_layer_id={} active_layer_name={}",
|
"active_layer_id={} active_layer_name={}",
|
||||||
layer.id, layer.name
|
layer.id, layer.name
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Command::SetActiveLayer { layer_id } => {
|
Command::SetActiveLayer { layer_id } => {
|
||||||
client.set_active_layer(layer_id).await?;
|
client.set_active_layer(layer_id)?;
|
||||||
println!("set_active_layer_id={}", layer_id);
|
println!("set_active_layer_id={}", layer_id);
|
||||||
}
|
}
|
||||||
Command::VisibleLayers => {
|
Command::VisibleLayers => {
|
||||||
let layers = client.get_visible_layers().await?;
|
let layers = client.get_visible_layers()?;
|
||||||
if layers.is_empty() {
|
if layers.is_empty() {
|
||||||
println!("no visible layers returned");
|
println!("no visible layers returned");
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -443,20 +439,18 @@ async fn run() -> Result<(), KiCadError> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command::SetVisibleLayers { layer_ids } => {
|
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());
|
println!("set_visible_layer_count={}", layer_ids.len());
|
||||||
}
|
}
|
||||||
Command::BoardOrigin { kind } => {
|
Command::BoardOrigin { kind } => {
|
||||||
let origin = client.get_board_origin(kind).await?;
|
let origin = client.get_board_origin(kind)?;
|
||||||
println!(
|
println!(
|
||||||
"origin_kind={} x_nm={} y_nm={}",
|
"origin_kind={} x_nm={} y_nm={}",
|
||||||
kind, origin.x_nm, origin.y_nm
|
kind, origin.x_nm, origin.y_nm
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Command::SetBoardOrigin { kind, x_nm, y_nm } => {
|
Command::SetBoardOrigin { kind, x_nm, y_nm } => {
|
||||||
client
|
client.set_board_origin(kind, Vector2Nm { x_nm, y_nm })?;
|
||||||
.set_board_origin(kind, Vector2Nm { x_nm, y_nm })
|
|
||||||
.await?;
|
|
||||||
println!("set_origin_kind={} x_nm={} y_nm={}", kind, x_nm, y_nm);
|
println!("set_origin_kind={} x_nm={} y_nm={}", kind, x_nm, y_nm);
|
||||||
}
|
}
|
||||||
Command::InjectDrcError {
|
Command::InjectDrcError {
|
||||||
|
|
@ -470,20 +464,18 @@ async fn run() -> Result<(), KiCadError> {
|
||||||
(Some(x_nm), Some(y_nm)) => Some(Vector2Nm { x_nm, y_nm }),
|
(Some(x_nm), Some(y_nm)) => Some(Vector2Nm { x_nm, y_nm }),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
let marker = client
|
let marker = client.inject_drc_error(severity, message, position, item_ids)?;
|
||||||
.inject_drc_error(severity, message, position, item_ids)
|
|
||||||
.await?;
|
|
||||||
println!(
|
println!(
|
||||||
"drc_marker_id={}",
|
"drc_marker_id={}",
|
||||||
marker.unwrap_or_else(|| "-".to_string())
|
marker.unwrap_or_else(|| "-".to_string())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Command::RefreshEditor { frame } => {
|
Command::RefreshEditor { frame } => {
|
||||||
client.refresh_editor(frame).await?;
|
client.refresh_editor(frame)?;
|
||||||
println!("refresh_editor=ok frame={}", frame);
|
println!("refresh_editor=ok frame={}", frame);
|
||||||
}
|
}
|
||||||
Command::BeginCommit => {
|
Command::BeginCommit => {
|
||||||
let session = client.begin_commit().await?;
|
let session = client.begin_commit()?;
|
||||||
println!("commit_id={}", session.id);
|
println!("commit_id={}", session.id);
|
||||||
}
|
}
|
||||||
Command::EndCommit {
|
Command::EndCommit {
|
||||||
|
|
@ -491,13 +483,11 @@ async fn run() -> Result<(), KiCadError> {
|
||||||
action,
|
action,
|
||||||
message,
|
message,
|
||||||
} => {
|
} => {
|
||||||
client
|
client.end_commit(CommitSession { id }, action, message)?;
|
||||||
.end_commit(CommitSession { id }, action, message)
|
|
||||||
.await?;
|
|
||||||
println!("end_commit=ok action={}", action);
|
println!("end_commit=ok action={}", action);
|
||||||
}
|
}
|
||||||
Command::SaveDoc => {
|
Command::SaveDoc => {
|
||||||
client.save_document().await?;
|
client.save_document()?;
|
||||||
println!("save_document=ok");
|
println!("save_document=ok");
|
||||||
}
|
}
|
||||||
Command::SaveCopy {
|
Command::SaveCopy {
|
||||||
|
|
@ -505,27 +495,25 @@ async fn run() -> Result<(), KiCadError> {
|
||||||
overwrite,
|
overwrite,
|
||||||
include_project,
|
include_project,
|
||||||
} => {
|
} => {
|
||||||
client
|
client.save_copy_of_document(path, overwrite, include_project)?;
|
||||||
.save_copy_of_document(path, overwrite, include_project)
|
|
||||||
.await?;
|
|
||||||
println!(
|
println!(
|
||||||
"save_copy_of_document=ok overwrite={} include_project={}",
|
"save_copy_of_document=ok overwrite={} include_project={}",
|
||||||
overwrite, include_project
|
overwrite, include_project
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Command::RevertDoc => {
|
Command::RevertDoc => {
|
||||||
client.revert_document().await?;
|
client.revert_document()?;
|
||||||
println!("revert_document=ok");
|
println!("revert_document=ok");
|
||||||
}
|
}
|
||||||
Command::RunAction { action } => {
|
Command::RunAction { action } => {
|
||||||
let status = client.run_action(action).await?;
|
let status = client.run_action(action)?;
|
||||||
println!("run_action_status={status:?}");
|
println!("run_action_status={status:?}");
|
||||||
}
|
}
|
||||||
Command::CreateItems {
|
Command::CreateItems {
|
||||||
items,
|
items,
|
||||||
container_id,
|
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());
|
println!("created_item_count={}", created.len());
|
||||||
for (index, item) in created.iter().enumerate() {
|
for (index, item) in created.iter().enumerate() {
|
||||||
println!(
|
println!(
|
||||||
|
|
@ -536,7 +524,7 @@ async fn run() -> Result<(), KiCadError> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command::UpdateItems { items } => {
|
Command::UpdateItems { items } => {
|
||||||
let updated = client.update_items(items).await?;
|
let updated = client.update_items(items)?;
|
||||||
println!("updated_item_count={}", updated.len());
|
println!("updated_item_count={}", updated.len());
|
||||||
for (index, item) in updated.iter().enumerate() {
|
for (index, item) in updated.iter().enumerate() {
|
||||||
println!(
|
println!(
|
||||||
|
|
@ -547,14 +535,14 @@ async fn run() -> Result<(), KiCadError> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command::DeleteItems { item_ids } => {
|
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());
|
println!("deleted_item_count={}", deleted.len());
|
||||||
for (index, item_id) in deleted.iter().enumerate() {
|
for (index, item_id) in deleted.iter().enumerate() {
|
||||||
println!("[{index}] id={item_id}");
|
println!("[{index}] id={item_id}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command::ParseCreateItemsFromString { contents } => {
|
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());
|
println!("created_item_count={}", created.len());
|
||||||
for (index, item) in created.iter().enumerate() {
|
for (index, item) in created.iter().enumerate() {
|
||||||
println!(
|
println!(
|
||||||
|
|
@ -565,32 +553,32 @@ async fn run() -> Result<(), KiCadError> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command::AddToSelection { item_ids } => {
|
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);
|
println!("selection_total={}", summary.total_items);
|
||||||
for entry in summary.type_url_counts {
|
for entry in summary.type_url_counts {
|
||||||
println!("type_url={} count={}", entry.type_url, entry.count);
|
println!("type_url={} count={}", entry.type_url, entry.count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command::RemoveFromSelection { item_ids } => {
|
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);
|
println!("selection_total={}", summary.total_items);
|
||||||
for entry in summary.type_url_counts {
|
for entry in summary.type_url_counts {
|
||||||
println!("type_url={} count={}", entry.type_url, entry.count);
|
println!("type_url={} count={}", entry.type_url, entry.count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command::ClearSelection => {
|
Command::ClearSelection => {
|
||||||
let summary = client.clear_selection().await?;
|
let summary = client.clear_selection()?;
|
||||||
println!("selection_total={}", summary.total_items);
|
println!("selection_total={}", summary.total_items);
|
||||||
}
|
}
|
||||||
Command::SelectionSummary => {
|
Command::SelectionSummary => {
|
||||||
let summary = client.get_selection_summary().await?;
|
let summary = client.get_selection_summary()?;
|
||||||
println!("selection_total={}", summary.total_items);
|
println!("selection_total={}", summary.total_items);
|
||||||
for entry in summary.type_url_counts {
|
for entry in summary.type_url_counts {
|
||||||
println!("type_url={} count={}", entry.type_url, entry.count);
|
println!("type_url={} count={}", entry.type_url, entry.count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command::SelectionDetails => {
|
Command::SelectionDetails => {
|
||||||
let details = client.get_selection_details().await?;
|
let details = client.get_selection_details()?;
|
||||||
println!("selection_total={}", details.len());
|
println!("selection_total={}", details.len());
|
||||||
for (index, item) in details.iter().enumerate() {
|
for (index, item) in details.iter().enumerate() {
|
||||||
println!(
|
println!(
|
||||||
|
|
@ -600,7 +588,7 @@ async fn run() -> Result<(), KiCadError> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command::SelectionRaw => {
|
Command::SelectionRaw => {
|
||||||
let items = client.get_selection_raw().await?;
|
let items = client.get_selection_raw()?;
|
||||||
println!("selection_total={}", items.len());
|
println!("selection_total={}", items.len());
|
||||||
for (index, item) in items.iter().enumerate() {
|
for (index, item) in items.iter().enumerate() {
|
||||||
println!(
|
println!(
|
||||||
|
|
@ -612,7 +600,7 @@ async fn run() -> Result<(), KiCadError> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command::NetlistPads => {
|
Command::NetlistPads => {
|
||||||
let entries = client.get_pad_netlist().await?;
|
let entries = client.get_pad_netlist()?;
|
||||||
println!("pad_net_entries={}", entries.len());
|
println!("pad_net_entries={}", entries.len());
|
||||||
for entry in entries {
|
for entry in entries {
|
||||||
println!(
|
println!(
|
||||||
|
|
@ -630,7 +618,7 @@ async fn run() -> Result<(), KiCadError> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command::ItemsById { item_ids } => {
|
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());
|
println!("items_total={}", details.len());
|
||||||
for (index, item) in details.iter().enumerate() {
|
for (index, item) in details.iter().enumerate() {
|
||||||
println!(
|
println!(
|
||||||
|
|
@ -643,9 +631,7 @@ async fn run() -> Result<(), KiCadError> {
|
||||||
item_ids,
|
item_ids,
|
||||||
include_child_text,
|
include_child_text,
|
||||||
} => {
|
} => {
|
||||||
let boxes = client
|
let boxes = client.get_item_bounding_boxes(item_ids, include_child_text)?;
|
||||||
.get_item_bounding_boxes(item_ids, include_child_text)
|
|
||||||
.await?;
|
|
||||||
println!("bbox_total={}", boxes.len());
|
println!("bbox_total={}", boxes.len());
|
||||||
for entry in boxes {
|
for entry in boxes {
|
||||||
println!(
|
println!(
|
||||||
|
|
@ -660,13 +646,11 @@ async fn run() -> Result<(), KiCadError> {
|
||||||
y_nm,
|
y_nm,
|
||||||
tolerance_nm,
|
tolerance_nm,
|
||||||
} => {
|
} => {
|
||||||
let result = client
|
let result = client.hit_test_item(item_id, Vector2Nm { x_nm, y_nm }, tolerance_nm)?;
|
||||||
.hit_test_item(item_id, Vector2Nm { x_nm, y_nm }, tolerance_nm)
|
|
||||||
.await?;
|
|
||||||
println!("hit_test={result}");
|
println!("hit_test={result}");
|
||||||
}
|
}
|
||||||
Command::PcbTypes => {
|
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);
|
println!("type_id={} type_name={}", entry.code, entry.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -674,9 +658,7 @@ async fn run() -> Result<(), KiCadError> {
|
||||||
type_codes,
|
type_codes,
|
||||||
include_debug,
|
include_debug,
|
||||||
} => {
|
} => {
|
||||||
let items = client
|
let items = client.get_items_raw_by_type_codes(type_codes.clone())?;
|
||||||
.get_items_raw_by_type_codes(type_codes.clone())
|
|
||||||
.await?;
|
|
||||||
println!(
|
println!(
|
||||||
"items_total={} requested_type_codes={:?}",
|
"items_total={} requested_type_codes={:?}",
|
||||||
items.len(),
|
items.len(),
|
||||||
|
|
@ -684,7 +666,7 @@ async fn run() -> Result<(), KiCadError> {
|
||||||
);
|
);
|
||||||
for (index, item) in items.iter().enumerate() {
|
for (index, item) in items.iter().enumerate() {
|
||||||
if include_debug {
|
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('\n', "\\n")
|
||||||
.replace('\t', " ");
|
.replace('\t', " ");
|
||||||
println!(
|
println!(
|
||||||
|
|
@ -705,11 +687,8 @@ async fn run() -> Result<(), KiCadError> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command::ItemsRawAllPcb { include_debug } => {
|
Command::ItemsRawAllPcb { include_debug } => {
|
||||||
for object_type in kicad_ipc::KiCadClient::pcb_object_type_codes() {
|
for object_type in kicad_ipc_rs::KiCadClient::pcb_object_type_codes() {
|
||||||
match client
|
match client.get_items_raw_by_type_codes(vec![object_type.code]) {
|
||||||
.get_items_raw_by_type_codes(vec![object_type.code])
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(items) => {
|
Ok(items) => {
|
||||||
println!(
|
println!(
|
||||||
"type_id={} type_name={} item_count={}",
|
"type_id={} type_name={} item_count={}",
|
||||||
|
|
@ -719,7 +698,7 @@ async fn run() -> Result<(), KiCadError> {
|
||||||
);
|
);
|
||||||
for (index, item) in items.iter().enumerate() {
|
for (index, item) in items.iter().enumerate() {
|
||||||
if include_debug {
|
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('\n', "\\n")
|
||||||
.replace('\t', " ");
|
.replace('\t', " ");
|
||||||
println!(
|
println!(
|
||||||
|
|
@ -753,9 +732,7 @@ async fn run() -> Result<(), KiCadError> {
|
||||||
layer_id,
|
layer_id,
|
||||||
include_debug,
|
include_debug,
|
||||||
} => {
|
} => {
|
||||||
let rows = client
|
let rows = client.get_pad_shape_as_polygon(pad_ids.clone(), layer_id)?;
|
||||||
.get_pad_shape_as_polygon(pad_ids.clone(), layer_id)
|
|
||||||
.await?;
|
|
||||||
println!(
|
println!(
|
||||||
"pad_shape_total={} layer_id={} requested_pad_count={}",
|
"pad_shape_total={} layer_id={} requested_pad_count={}",
|
||||||
rows.len(),
|
rows.len(),
|
||||||
|
|
@ -779,11 +756,9 @@ async fn run() -> Result<(), KiCadError> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if include_debug {
|
if include_debug {
|
||||||
let raw_chunks = client
|
let raw_chunks = client.get_pad_shape_as_polygon_raw(pad_ids, layer_id)?;
|
||||||
.get_pad_shape_as_polygon_raw(pad_ids, layer_id)
|
|
||||||
.await?;
|
|
||||||
for (chunk_index, chunk) in raw_chunks.iter().enumerate() {
|
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('\n', "\\n")
|
||||||
.replace('\t', " ");
|
.replace('\t', " ");
|
||||||
println!("raw_chunk={chunk_index} debug={debug}");
|
println!("raw_chunk={chunk_index} debug={debug}");
|
||||||
|
|
@ -795,9 +770,8 @@ async fn run() -> Result<(), KiCadError> {
|
||||||
layer_ids,
|
layer_ids,
|
||||||
include_debug,
|
include_debug,
|
||||||
} => {
|
} => {
|
||||||
let rows = client
|
let rows =
|
||||||
.check_padstack_presence_on_layers(item_ids.clone(), layer_ids.clone())
|
client.check_padstack_presence_on_layers(item_ids.clone(), layer_ids.clone())?;
|
||||||
.await?;
|
|
||||||
println!(
|
println!(
|
||||||
"padstack_presence_total={} requested_item_count={} requested_layer_count={}",
|
"padstack_presence_total={} requested_item_count={} requested_layer_count={}",
|
||||||
rows.len(),
|
rows.len(),
|
||||||
|
|
@ -811,11 +785,10 @@ async fn run() -> Result<(), KiCadError> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if include_debug {
|
if include_debug {
|
||||||
let raw_chunks = client
|
let raw_chunks =
|
||||||
.check_padstack_presence_on_layers_raw(item_ids, layer_ids)
|
client.check_padstack_presence_on_layers_raw(item_ids, layer_ids)?;
|
||||||
.await?;
|
|
||||||
for (chunk_index, chunk) in raw_chunks.iter().enumerate() {
|
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('\n', "\\n")
|
||||||
.replace('\t', " ");
|
.replace('\t', " ");
|
||||||
println!("raw_chunk={chunk_index} debug={debug}");
|
println!("raw_chunk={chunk_index} debug={debug}");
|
||||||
|
|
@ -823,7 +796,7 @@ async fn run() -> Result<(), KiCadError> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command::TitleBlock => {
|
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!("title={}", title_block.title);
|
||||||
println!("date={}", title_block.date);
|
println!("date={}", title_block.date);
|
||||||
println!("revision={}", title_block.revision);
|
println!("revision={}", title_block.revision);
|
||||||
|
|
@ -833,28 +806,28 @@ async fn run() -> Result<(), KiCadError> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Command::BoardAsString => {
|
Command::BoardAsString => {
|
||||||
let content = client.get_board_as_string().await?;
|
let content = client.get_board_as_string()?;
|
||||||
println!("{content}");
|
println!("{content}");
|
||||||
}
|
}
|
||||||
Command::SelectionAsString => {
|
Command::SelectionAsString => {
|
||||||
let content = client.get_selection_as_string().await?;
|
let content = client.get_selection_as_string()?;
|
||||||
println!("{content}");
|
println!("{content}");
|
||||||
}
|
}
|
||||||
Command::Stackup => {
|
Command::Stackup => {
|
||||||
let stackup = client.get_board_stackup().await?;
|
let stackup = client.get_board_stackup()?;
|
||||||
println!("{stackup:#?}");
|
println!("{stackup:#?}");
|
||||||
}
|
}
|
||||||
Command::UpdateStackup => {
|
Command::UpdateStackup => {
|
||||||
let stackup = client.get_board_stackup().await?;
|
let stackup = client.get_board_stackup()?;
|
||||||
let updated = client.update_board_stackup(stackup).await?;
|
let updated = client.update_board_stackup(stackup)?;
|
||||||
println!("{updated:#?}");
|
println!("{updated:#?}");
|
||||||
}
|
}
|
||||||
Command::GraphicsDefaults => {
|
Command::GraphicsDefaults => {
|
||||||
let defaults = client.get_graphics_defaults().await?;
|
let defaults = client.get_graphics_defaults()?;
|
||||||
println!("{defaults:#?}");
|
println!("{defaults:#?}");
|
||||||
}
|
}
|
||||||
Command::Appearance => {
|
Command::Appearance => {
|
||||||
let appearance = client.get_board_editor_appearance_settings().await?;
|
let appearance = client.get_board_editor_appearance_settings()?;
|
||||||
println!("{appearance:#?}");
|
println!("{appearance:#?}");
|
||||||
}
|
}
|
||||||
Command::SetAppearance {
|
Command::SetAppearance {
|
||||||
|
|
@ -863,31 +836,31 @@ async fn run() -> Result<(), KiCadError> {
|
||||||
board_flip,
|
board_flip,
|
||||||
ratsnest_display,
|
ratsnest_display,
|
||||||
} => {
|
} => {
|
||||||
let updated = client
|
let updated = client.set_board_editor_appearance_settings(
|
||||||
.set_board_editor_appearance_settings(kicad_ipc::BoardEditorAppearanceSettings {
|
kicad_ipc_rs::BoardEditorAppearanceSettings {
|
||||||
inactive_layer_display,
|
inactive_layer_display,
|
||||||
net_color_display,
|
net_color_display,
|
||||||
board_flip,
|
board_flip,
|
||||||
ratsnest_display,
|
ratsnest_display,
|
||||||
})
|
},
|
||||||
.await?;
|
)?;
|
||||||
println!("{updated:#?}");
|
println!("{updated:#?}");
|
||||||
}
|
}
|
||||||
Command::RefillZones { zone_ids } => {
|
Command::RefillZones { zone_ids } => {
|
||||||
client.refill_zones(zone_ids).await?;
|
client.refill_zones(zone_ids)?;
|
||||||
println!("refill_zones_dispatched=ok");
|
println!("refill_zones_dispatched=ok");
|
||||||
}
|
}
|
||||||
Command::InteractiveMoveItems { item_ids } => {
|
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());
|
println!("interactive_move_item_count={}", item_ids.len());
|
||||||
}
|
}
|
||||||
Command::NetClass => {
|
Command::NetClass => {
|
||||||
let nets = client.get_nets().await?;
|
let nets = client.get_nets()?;
|
||||||
let netclasses = client.get_netclass_for_nets(nets).await?;
|
let netclasses = client.get_netclass_for_nets(nets)?;
|
||||||
println!("{netclasses:#?}");
|
println!("{netclasses:#?}");
|
||||||
}
|
}
|
||||||
Command::BoardReadReport { output } => {
|
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 {
|
fs::write(&output, report).map_err(|err| KiCadError::Config {
|
||||||
reason: format!("failed to write report to `{}`: {err}", output.display()),
|
reason: format!("failed to write report to `{}`: {err}", output.display()),
|
||||||
})?;
|
})?;
|
||||||
|
|
@ -897,9 +870,9 @@ async fn run() -> Result<(), KiCadError> {
|
||||||
print_proto_coverage_board_read();
|
print_proto_coverage_board_read();
|
||||||
}
|
}
|
||||||
Command::Smoke => {
|
Command::Smoke => {
|
||||||
client.ping().await?;
|
client.ping()?;
|
||||||
let version = client.get_version().await?;
|
let version = client.get_version()?;
|
||||||
let has_board = client.has_open_board().await?;
|
let has_board = client.has_open_board()?;
|
||||||
println!(
|
println!(
|
||||||
"smoke ok: version={}.{}.{} board_open={}",
|
"smoke ok: version={}.{}.{} board_open={}",
|
||||||
version.major, version.minor, version.patch, has_board
|
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();
|
let mut out = String::new();
|
||||||
out.push_str("# Board Read Reconstruction Report\n\n");
|
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("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");
|
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("## Session\n\n");
|
||||||
out.push_str(&format!(
|
out.push_str(&format!(
|
||||||
"- KiCad version: {}.{}.{} ({})\n",
|
"- 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");
|
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() {
|
if docs.is_empty() {
|
||||||
out.push_str("- No open PCB docs\n\n");
|
out.push_str("- No open PCB docs\n\n");
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -2230,7 +2203,7 @@ async fn build_board_read_report_markdown(client: &KiCadClient) -> Result<String
|
||||||
}
|
}
|
||||||
|
|
||||||
out.push_str("## Layer / Origin / Nets\n\n");
|
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();
|
let enabled_layers = enabled.layers.clone();
|
||||||
out.push_str(&format!(
|
out.push_str(&format!(
|
||||||
"- copper_layer_count: {}\n",
|
"- 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));
|
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");
|
out.push_str("- visible_layers:\n");
|
||||||
for layer in visible_layers {
|
for layer in visible_layers {
|
||||||
out.push_str(&format!(" - {} ({})\n", layer.name, layer.id));
|
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!(
|
out.push_str(&format!(
|
||||||
"- active_layer: {} ({})\n",
|
"- active_layer: {} ({})\n",
|
||||||
active_layer.name, active_layer.id
|
active_layer.name, active_layer.id
|
||||||
));
|
));
|
||||||
|
|
||||||
let grid_origin = client
|
let grid_origin = client.get_board_origin(kicad_ipc_rs::BoardOriginKind::Grid)?;
|
||||||
.get_board_origin(kicad_ipc::BoardOriginKind::Grid)
|
|
||||||
.await?;
|
|
||||||
out.push_str(&format!(
|
out.push_str(&format!(
|
||||||
"- grid_origin_nm: {},{}\n",
|
"- grid_origin_nm: {},{}\n",
|
||||||
grid_origin.x_nm, grid_origin.y_nm
|
grid_origin.x_nm, grid_origin.y_nm
|
||||||
));
|
));
|
||||||
let drill_origin = client
|
let drill_origin = client.get_board_origin(kicad_ipc_rs::BoardOriginKind::Drill)?;
|
||||||
.get_board_origin(kicad_ipc::BoardOriginKind::Drill)
|
|
||||||
.await?;
|
|
||||||
out.push_str(&format!(
|
out.push_str(&format!(
|
||||||
"- drill_origin_nm: {},{}\n",
|
"- drill_origin_nm: {},{}\n",
|
||||||
drill_origin.x_nm, drill_origin.y_nm
|
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(&format!("- net_count: {}\n", nets.len()));
|
||||||
out.push_str("\n### Netlist\n\n");
|
out.push_str("\n### Netlist\n\n");
|
||||||
for net in &nets {
|
for net in &nets {
|
||||||
|
|
@ -2277,7 +2246,7 @@ async fn build_board_read_report_markdown(client: &KiCadClient) -> Result<String
|
||||||
out.push('\n');
|
out.push('\n');
|
||||||
|
|
||||||
out.push_str("### Pad-Level Netlist (Footprint/Pad/Net)\n\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();
|
let mut pad_ids = BTreeSet::new();
|
||||||
out.push_str(&format!("- pad_entry_count: {}\n", pad_entries.len()));
|
out.push_str(&format!("- pad_entry_count: {}\n", pad_entries.len()));
|
||||||
for (index, entry) in pad_entries.iter().enumerate() {
|
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 mut present_pad_ids_by_layer: BTreeMap<i32, BTreeSet<String>> = BTreeMap::new();
|
||||||
let presence_rows = client
|
let presence_rows =
|
||||||
.check_padstack_presence_on_layers(pad_ids.clone(), enabled_layer_ids)
|
client.check_padstack_presence_on_layers(pad_ids.clone(), enabled_layer_ids)?;
|
||||||
.await?;
|
|
||||||
out.push_str(&format!(
|
out.push_str(&format!(
|
||||||
"- presence_entry_count: {}\n",
|
"- presence_entry_count: {}\n",
|
||||||
presence_rows.len()
|
presence_rows.len()
|
||||||
|
|
@ -2372,9 +2340,7 @@ async fn build_board_read_report_markdown(client: &KiCadClient) -> Result<String
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let polygons = client
|
let polygons = client.get_pad_shape_as_polygon(pad_ids_on_layer, layer.id)?;
|
||||||
.get_pad_shape_as_polygon(pad_ids_on_layer, layer.id)
|
|
||||||
.await?;
|
|
||||||
out.push_str(&format!("- polygon_entry_count: {}\n\n", polygons.len()));
|
out.push_str(&format!("- polygon_entry_count: {}\n\n", polygons.len()));
|
||||||
for row in polygons {
|
for row in polygons {
|
||||||
let summary = polygon_geometry_summary(&row.polygon);
|
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("## Board/Editor Structures\n\n");
|
||||||
out.push_str("### Title Block\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!("- title: {}\n", title_block.title));
|
||||||
out.push_str(&format!("- date: {}\n", title_block.date));
|
out.push_str(&format!("- date: {}\n", title_block.date));
|
||||||
out.push_str(&format!("- revision: {}\n", title_block.revision));
|
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('\n');
|
||||||
|
|
||||||
out.push_str("### Stackup\n\n```text\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("\n```\n\n");
|
||||||
|
|
||||||
out.push_str("### Graphics Defaults\n\n```text\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("\n```\n\n");
|
||||||
|
|
||||||
out.push_str("### Editor Appearance\n\n```text\n");
|
out.push_str("### Editor Appearance\n\n```text\n");
|
||||||
out.push_str(&format!(
|
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("\n```\n\n");
|
||||||
|
|
||||||
out.push_str("### NetClass Map\n\n```text\n");
|
out.push_str("### NetClass Map\n\n```text\n");
|
||||||
out.push_str(&format!(
|
out.push_str(&format!(
|
||||||
"{:#?}",
|
"{:#?}",
|
||||||
client
|
client.get_netclass_for_nets(client.get_nets()?)?
|
||||||
.get_netclass_for_nets(client.get_nets().await?)
|
|
||||||
.await?
|
|
||||||
));
|
));
|
||||||
out.push_str("\n```\n\n");
|
out.push_str("\n```\n\n");
|
||||||
|
|
||||||
out.push_str("## PCB Item Coverage (All KOT_PCB_* Types)\n\n");
|
out.push_str("## PCB Item Coverage (All KOT_PCB_* Types)\n\n");
|
||||||
let mut missing_types: Vec<PcbObjectTypeCode> = Vec::new();
|
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!(
|
out.push_str(&format!(
|
||||||
"### {} ({})\n\n",
|
"### {} ({})\n\n",
|
||||||
object_type.name, object_type.code
|
object_type.name, object_type.code
|
||||||
));
|
));
|
||||||
match client
|
match client.get_items_raw_by_type_codes(vec![object_type.code]) {
|
||||||
.get_items_raw_by_type_codes(vec![object_type.code])
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(items) => {
|
Ok(items) => {
|
||||||
if items.is_empty() {
|
if items.is_empty() {
|
||||||
missing_types.push(*object_type);
|
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)
|
.take(REPORT_MAX_ITEM_DEBUG_ROWS_PER_TYPE)
|
||||||
.enumerate()
|
.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 {
|
if debug.len() > REPORT_MAX_ITEM_DEBUG_CHARS {
|
||||||
debug.truncate(REPORT_MAX_ITEM_DEBUG_CHARS);
|
debug.truncate(REPORT_MAX_ITEM_DEBUG_CHARS);
|
||||||
debug.push_str("\n...<truncated; use items-raw CLI for full payload>");
|
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");
|
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 {
|
if board_text.len() > REPORT_MAX_BOARD_SNAPSHOT_CHARS {
|
||||||
board_text.truncate(REPORT_MAX_BOARD_SNAPSHOT_CHARS);
|
board_text.truncate(REPORT_MAX_BOARD_SNAPSHOT_CHARS);
|
||||||
board_text.push_str(
|
board_text.push_str(
|
||||||
|
|
@ -2665,7 +2626,7 @@ struct PolygonGeometrySummary {
|
||||||
arc_nodes: usize,
|
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 {
|
let mut summary = PolygonGeometrySummary {
|
||||||
hole_count: polygon.holes.len(),
|
hole_count: polygon.holes.len(),
|
||||||
..PolygonGeometrySummary::default()
|
..PolygonGeometrySummary::default()
|
||||||
|
|
@ -2675,8 +2636,8 @@ fn polygon_geometry_summary(polygon: &kicad_ipc::PolygonWithHolesNm) -> PolygonG
|
||||||
summary.outline_nodes = outline.nodes.len();
|
summary.outline_nodes = outline.nodes.len();
|
||||||
for node in &outline.nodes {
|
for node in &outline.nodes {
|
||||||
match node {
|
match node {
|
||||||
kicad_ipc::PolyLineNodeGeometryNm::Point(_) => summary.point_nodes += 1,
|
kicad_ipc_rs::PolyLineNodeGeometryNm::Point(_) => summary.point_nodes += 1,
|
||||||
kicad_ipc::PolyLineNodeGeometryNm::Arc(_) => summary.arc_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();
|
summary.hole_nodes_total += hole.nodes.len();
|
||||||
for node in &hole.nodes {
|
for node in &hole.nodes {
|
||||||
match node {
|
match node {
|
||||||
kicad_ipc::PolyLineNodeGeometryNm::Point(_) => summary.point_nodes += 1,
|
kicad_ipc_rs::PolyLineNodeGeometryNm::Point(_) => summary.point_nodes += 1,
|
||||||
kicad_ipc::PolyLineNodeGeometryNm::Arc(_) => summary.arc_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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{parse_args_from, Command};
|
use super::{parse_args_from, Command};
|
||||||
use kicad_ipc::{
|
use kicad_ipc_rs::{
|
||||||
BoardFlipMode, BoardOriginKind, CommitAction, DrcSeverity, InactiveLayerDisplayMode,
|
BoardFlipMode, BoardOriginKind, CommitAction, DrcSeverity, InactiveLayerDisplayMode,
|
||||||
NetColorDisplayMode, RatsnestDisplayMode,
|
NetColorDisplayMode, RatsnestDisplayMode,
|
||||||
};
|
};
|
||||||
|
|
@ -2930,7 +2891,7 @@ mod tests {
|
||||||
|
|
||||||
match command {
|
match command {
|
||||||
Command::SetNetClasses { merge_mode } => {
|
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:?}"),
|
other => panic!("unexpected command variant: {other:?}"),
|
||||||
}
|
}
|
||||||
|
|
@ -2952,7 +2913,7 @@ mod tests {
|
||||||
merge_mode,
|
merge_mode,
|
||||||
variables,
|
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"));
|
assert_eq!(variables.get("REV").map(|value| value.as_str()), Some("A"));
|
||||||
}
|
}
|
||||||
other => panic!("unexpected command variant: {other:?}"),
|
other => panic!("unexpected command variant: {other:?}"),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue