fix: reduce selection API lossiness in existing public methods (#19)
* fix(selection): preserve selection payload fidelity in existing APIs * docs(selection): update deep-dump example for revised selection signatures * docs(selection): document revised selection signatures and return models
This commit is contained in:
parent
e1c83bf561
commit
b32eb7fa44
10
README.md
10
README.md
|
|
@ -161,14 +161,14 @@ Legend:
|
|||
| `UpdateItems` | Implemented | `KiCadClient::update_items_raw`, `KiCadClient::update_items` |
|
||||
| `DeleteItems` | Implemented | `KiCadClient::delete_items_raw`, `KiCadClient::delete_items` |
|
||||
| `GetBoundingBox` | Implemented | `KiCadClient::get_item_bounding_boxes` |
|
||||
| `GetSelection` | Implemented | `KiCadClient::get_selection_raw`, `KiCadClient::get_selection`, `KiCadClient::get_selection_summary`, `KiCadClient::get_selection_details` |
|
||||
| `AddToSelection` | Implemented | `KiCadClient::add_to_selection_raw`, `KiCadClient::add_to_selection` |
|
||||
| `RemoveFromSelection` | Implemented | `KiCadClient::remove_from_selection_raw`, `KiCadClient::remove_from_selection` |
|
||||
| `ClearSelection` | Implemented | `KiCadClient::clear_selection_raw`, `KiCadClient::clear_selection` |
|
||||
| `GetSelection` | Implemented | `KiCadClient::get_selection_raw(type_codes)`, `KiCadClient::get_selection(type_codes)`, `KiCadClient::get_selection_summary(type_codes)`, `KiCadClient::get_selection_details(type_codes)` |
|
||||
| `AddToSelection` | Implemented | `KiCadClient::add_to_selection_raw`, `KiCadClient::add_to_selection` (`SelectionMutationResult`) |
|
||||
| `RemoveFromSelection` | Implemented | `KiCadClient::remove_from_selection_raw`, `KiCadClient::remove_from_selection` (`SelectionMutationResult`) |
|
||||
| `ClearSelection` | Implemented | `KiCadClient::clear_selection_raw`, `KiCadClient::clear_selection` (`SelectionMutationResult`) |
|
||||
| `HitTest` | Implemented | `KiCadClient::hit_test_item` |
|
||||
| `GetTitleBlockInfo` | Implemented | `KiCadClient::get_title_block_info` |
|
||||
| `SaveDocumentToString` | Implemented | `KiCadClient::get_board_as_string` |
|
||||
| `SaveSelectionToString` | Implemented | `KiCadClient::get_selection_as_string` |
|
||||
| `SaveSelectionToString` | Implemented | `KiCadClient::get_selection_as_string` (`SelectionStringDump { ids, contents }`) |
|
||||
| `ParseAndCreateItemsFromString` | Implemented | `KiCadClient::parse_and_create_items_from_string_raw`, `KiCadClient::parse_and_create_items_from_string` |
|
||||
|
||||
### Project manager
|
||||
|
|
|
|||
|
|
@ -0,0 +1,83 @@
|
|||
# PCB Selection Deep Dump
|
||||
|
||||
How to extract maximum data for current PCB selection using `kicad-ipc-rs` bindings.
|
||||
|
||||
## Script
|
||||
|
||||
Use:
|
||||
|
||||
- `examples/selection_deep_dump.rs`
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
RUSTFLAGS='-Awarnings' cargo run -q --features blocking --example selection_deep_dump
|
||||
```
|
||||
|
||||
## What It Collects
|
||||
|
||||
- Selection summary:
|
||||
- total selected item count
|
||||
- per-type counts (`type_url`)
|
||||
- Decoded selected items (`get_selection(Vec::new())`)
|
||||
- Raw selected payload metadata (`get_selection_raw(Vec::new())`)
|
||||
- Human-readable selection rows (`get_selection_details(Vec::new())`)
|
||||
- Item IDs for all selected items
|
||||
- Item bounding boxes (`get_item_bounding_boxes`)
|
||||
- Footprint-level fields:
|
||||
- reference
|
||||
- UUID
|
||||
- position (nm)
|
||||
- orientation (deg)
|
||||
- layer
|
||||
- pad count
|
||||
- Designator/value pairs from selection text (`get_selection_as_string().contents`)
|
||||
- Selection text dump item IDs (`get_selection_as_string().ids`)
|
||||
- Pad-level net rows for selected footprints (`get_pad_netlist`)
|
||||
- Net graph among selected references (`net -> refs`)
|
||||
- Net name to net code mapping (`get_nets`)
|
||||
|
||||
## API Sequence (Bindings)
|
||||
|
||||
- `KiCadClientBlocking::connect`
|
||||
- `ping`
|
||||
- `get_version`
|
||||
- `get_selection_summary(Vec::new())`
|
||||
- `get_selection(Vec::new())`
|
||||
- `get_selection_raw(Vec::new())`
|
||||
- `get_selection_details(Vec::new())`
|
||||
- `get_item_bounding_boxes`
|
||||
- `get_selection_as_string`
|
||||
- `get_pad_netlist`
|
||||
- `get_nets`
|
||||
- `get_items_by_net` (best path; may be unavailable)
|
||||
- fallback: `get_items_by_type_codes` per type code + local net-name filter
|
||||
|
||||
## Route/Trace Discovery Logic
|
||||
|
||||
Primary:
|
||||
|
||||
- Query `get_items_by_net` with types:
|
||||
- `KOT_PCB_TRACE`
|
||||
- `KOT_PCB_VIA`
|
||||
- `KOT_PCB_ARC`
|
||||
- `KOT_PCB_ZONE`
|
||||
- `KOT_PCB_PAD`
|
||||
- `KOT_PCB_SHAPE`
|
||||
|
||||
Fallback when `GetItemsByNet` is unsupported:
|
||||
|
||||
- Query each type separately with `get_items_by_type_codes(vec![type_code])`
|
||||
- Filter returned items locally by `item.net.name in selected_net_names`
|
||||
|
||||
## Known KiCad 10.0.0-rc1 Behavior Seen
|
||||
|
||||
- `kiapi.board.commands.GetItemsByNet` can return `AS_UNHANDLED`.
|
||||
- Script handles this and continues with fallback scan.
|
||||
- On current session, fallback returned pad-connected items; no extra tracks/vias/arcs were returned for selected net names.
|
||||
|
||||
## Notes
|
||||
|
||||
- Script is read-only.
|
||||
- Retries are built in for transient API unhandled/timeouts.
|
||||
- Output is verbose by design, intended for debugging and data mining.
|
||||
|
|
@ -237,6 +237,8 @@ Show summary of current PCB selection by item type:
|
|||
cargo run --features blocking --bin kicad-ipc-cli -- selection-summary
|
||||
```
|
||||
|
||||
Note: CLI uses `Vec::new()` for `type_codes` on `get_selection_summary`, meaning unfiltered selection.
|
||||
|
||||
Show parsed details for currently selected items:
|
||||
|
||||
```bash
|
||||
|
|
@ -255,6 +257,8 @@ Add items to current selection:
|
|||
cargo run --features blocking --bin kicad-ipc-cli -- add-to-selection --id <uuid> --id <uuid>
|
||||
```
|
||||
|
||||
Output now comes from `SelectionMutationResult` (`summary` + decoded `items`).
|
||||
|
||||
Remove items from current selection:
|
||||
|
||||
```bash
|
||||
|
|
@ -339,6 +343,8 @@ Dump selection text (KiCad s-expression):
|
|||
cargo run --features blocking --bin kicad-ipc-cli -- selection-as-string
|
||||
```
|
||||
|
||||
Output includes `selection_id_count`, one `id=` line per selected item, then the `contents` text.
|
||||
|
||||
Dump title block fields:
|
||||
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -10,3 +10,9 @@ Key items:
|
|||
- `KiCadClientBlocking` (`blocking` feature)
|
||||
- `KiCadError`
|
||||
- Typed models under `model::*`
|
||||
|
||||
Selection API notes:
|
||||
|
||||
- `get_selection_*` methods now take `type_codes: Vec<i32>` (`Vec::new()` means no filter).
|
||||
- `add_to_selection`, `remove_from_selection`, `clear_selection` return `SelectionMutationResult` (decoded items + summary).
|
||||
- `get_selection_as_string` returns `SelectionStringDump` (`ids` + `contents`).
|
||||
|
|
|
|||
|
|
@ -0,0 +1,631 @@
|
|||
#[cfg(feature = "blocking")]
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
#[cfg(feature = "blocking")]
|
||||
use std::thread::sleep;
|
||||
#[cfg(feature = "blocking")]
|
||||
use std::time::Duration;
|
||||
|
||||
#[cfg(feature = "blocking")]
|
||||
use kicad_ipc_rs::{
|
||||
KiCadClient, KiCadClientBlocking, KiCadError, PcbArc, PcbBoardGraphicShape, PcbBoardText,
|
||||
PcbBoardTextBox, PcbDimension, PcbField, PcbFootprint, PcbGroup, PcbItem, PcbPad, PcbTrack,
|
||||
PcbVia, PcbZone,
|
||||
};
|
||||
|
||||
#[cfg(feature = "blocking")]
|
||||
fn retry<T, F>(label: &str, mut op: F) -> Result<T, KiCadError>
|
||||
where
|
||||
F: FnMut() -> Result<T, KiCadError>,
|
||||
{
|
||||
let attempts = 6;
|
||||
for attempt in 1..=attempts {
|
||||
match op() {
|
||||
Ok(value) => return Ok(value),
|
||||
Err(err) => {
|
||||
if attempt == attempts {
|
||||
return Err(err);
|
||||
}
|
||||
eprintln!("warn: {label} attempt {attempt} failed: {err}");
|
||||
sleep(Duration::from_millis(300));
|
||||
}
|
||||
}
|
||||
}
|
||||
unreachable!("attempt loop exits via return");
|
||||
}
|
||||
|
||||
#[cfg(feature = "blocking")]
|
||||
fn item_id(item: &PcbItem) -> Option<&str> {
|
||||
match item {
|
||||
PcbItem::Track(v) => v.id.as_deref(),
|
||||
PcbItem::Arc(v) => v.id.as_deref(),
|
||||
PcbItem::Via(v) => v.id.as_deref(),
|
||||
PcbItem::Footprint(v) => v.id.as_deref(),
|
||||
PcbItem::Pad(v) => v.id.as_deref(),
|
||||
PcbItem::BoardGraphicShape(v) => v.id.as_deref(),
|
||||
PcbItem::BoardText(v) => v.id.as_deref(),
|
||||
PcbItem::BoardTextBox(v) => v.id.as_deref(),
|
||||
PcbItem::Field(_) => None,
|
||||
PcbItem::Zone(v) => v.id.as_deref(),
|
||||
PcbItem::Dimension(v) => v.id.as_deref(),
|
||||
PcbItem::Group(v) => v.id.as_deref(),
|
||||
PcbItem::Unknown(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "blocking")]
|
||||
fn pcb_type_code(name: &str) -> Option<i32> {
|
||||
KiCadClient::pcb_object_type_codes()
|
||||
.iter()
|
||||
.find(|entry| entry.name == name)
|
||||
.map(|entry| entry.code)
|
||||
}
|
||||
|
||||
#[cfg(feature = "blocking")]
|
||||
fn extract_property(line: &str, key: &str) -> Option<String> {
|
||||
let marker = format!("(property \"{key}\" \"");
|
||||
let start = line.find(&marker)?;
|
||||
let rest = &line[start + marker.len()..];
|
||||
let end = rest.find('"')?;
|
||||
Some(rest[..end].to_string())
|
||||
}
|
||||
|
||||
#[cfg(feature = "blocking")]
|
||||
fn parse_ref_values_from_selection_sexpr(contents: &str) -> BTreeMap<String, String> {
|
||||
let mut refs = Vec::new();
|
||||
let mut vals = Vec::new();
|
||||
for line in contents.lines() {
|
||||
if let Some(reference) = extract_property(line, "Reference") {
|
||||
refs.push(reference);
|
||||
}
|
||||
if let Some(value) = extract_property(line, "Value") {
|
||||
vals.push(value);
|
||||
}
|
||||
}
|
||||
|
||||
let mut out = BTreeMap::new();
|
||||
let limit = refs.len().min(vals.len());
|
||||
for index in 0..limit {
|
||||
out.insert(refs[index].clone(), vals[index].clone());
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
#[cfg(feature = "blocking")]
|
||||
fn print_item(item: &PcbItem) {
|
||||
match item {
|
||||
PcbItem::Track(PcbTrack {
|
||||
id,
|
||||
start_nm,
|
||||
end_nm,
|
||||
width_nm,
|
||||
layer,
|
||||
net,
|
||||
..
|
||||
}) => {
|
||||
println!(
|
||||
" track id={} start={:?} end={:?} width_nm={:?} layer={} net={}",
|
||||
id.as_deref().unwrap_or("-"),
|
||||
start_nm.map(|v| (v.x_nm, v.y_nm)),
|
||||
end_nm.map(|v| (v.x_nm, v.y_nm)),
|
||||
width_nm,
|
||||
layer.name,
|
||||
net.as_ref().map(|v| v.name.as_str()).unwrap_or("-")
|
||||
);
|
||||
}
|
||||
PcbItem::Arc(PcbArc {
|
||||
id,
|
||||
start_nm,
|
||||
mid_nm,
|
||||
end_nm,
|
||||
width_nm,
|
||||
layer,
|
||||
net,
|
||||
..
|
||||
}) => {
|
||||
println!(
|
||||
" arc id={} start={:?} mid={:?} end={:?} width_nm={:?} layer={} net={}",
|
||||
id.as_deref().unwrap_or("-"),
|
||||
start_nm.map(|v| (v.x_nm, v.y_nm)),
|
||||
mid_nm.map(|v| (v.x_nm, v.y_nm)),
|
||||
end_nm.map(|v| (v.x_nm, v.y_nm)),
|
||||
width_nm,
|
||||
layer.name,
|
||||
net.as_ref().map(|v| v.name.as_str()).unwrap_or("-")
|
||||
);
|
||||
}
|
||||
PcbItem::Via(PcbVia {
|
||||
id,
|
||||
position_nm,
|
||||
via_type,
|
||||
layers,
|
||||
net,
|
||||
..
|
||||
}) => {
|
||||
let stack = layers
|
||||
.as_ref()
|
||||
.map(|l| {
|
||||
l.padstack_layers
|
||||
.iter()
|
||||
.map(|x| x.name.clone())
|
||||
.collect::<Vec<_>>()
|
||||
.join(",")
|
||||
})
|
||||
.unwrap_or_else(|| "-".to_string());
|
||||
println!(
|
||||
" via id={} pos={:?} via_type={:?} layers={} net={}",
|
||||
id.as_deref().unwrap_or("-"),
|
||||
position_nm.map(|v| (v.x_nm, v.y_nm)),
|
||||
via_type,
|
||||
stack,
|
||||
net.as_ref().map(|v| v.name.as_str()).unwrap_or("-")
|
||||
);
|
||||
}
|
||||
PcbItem::Footprint(PcbFootprint {
|
||||
id,
|
||||
reference,
|
||||
position_nm,
|
||||
orientation_deg,
|
||||
layer,
|
||||
pad_count,
|
||||
..
|
||||
}) => {
|
||||
println!(
|
||||
" footprint id={} ref={} pos={:?} orientation_deg={:?} layer={} pad_count={}",
|
||||
id.as_deref().unwrap_or("-"),
|
||||
reference.as_deref().unwrap_or("-"),
|
||||
position_nm.map(|v| (v.x_nm, v.y_nm)),
|
||||
orientation_deg,
|
||||
layer.name,
|
||||
pad_count
|
||||
);
|
||||
}
|
||||
PcbItem::Pad(PcbPad {
|
||||
id,
|
||||
number,
|
||||
pad_type,
|
||||
position_nm,
|
||||
net,
|
||||
..
|
||||
}) => {
|
||||
println!(
|
||||
" pad id={} number={} pad_type={:?} pos={:?} net={}",
|
||||
id.as_deref().unwrap_or("-"),
|
||||
number,
|
||||
pad_type,
|
||||
position_nm.map(|v| (v.x_nm, v.y_nm)),
|
||||
net.as_ref().map(|v| v.name.as_str()).unwrap_or("-")
|
||||
);
|
||||
}
|
||||
PcbItem::BoardGraphicShape(PcbBoardGraphicShape {
|
||||
id,
|
||||
layer,
|
||||
net,
|
||||
geometry_kind,
|
||||
..
|
||||
}) => {
|
||||
println!(
|
||||
" shape id={} layer={} geometry={} net={}",
|
||||
id.as_deref().unwrap_or("-"),
|
||||
layer.name,
|
||||
geometry_kind.as_deref().unwrap_or("-"),
|
||||
net.as_ref().map(|v| v.name.as_str()).unwrap_or("-")
|
||||
);
|
||||
}
|
||||
PcbItem::BoardText(PcbBoardText {
|
||||
id, layer, text, ..
|
||||
}) => {
|
||||
println!(
|
||||
" text id={} layer={} text={}",
|
||||
id.as_deref().unwrap_or("-"),
|
||||
layer.name,
|
||||
text.as_deref().unwrap_or("-")
|
||||
);
|
||||
}
|
||||
PcbItem::BoardTextBox(PcbBoardTextBox {
|
||||
id, layer, text, ..
|
||||
}) => {
|
||||
println!(
|
||||
" textbox id={} layer={} text={}",
|
||||
id.as_deref().unwrap_or("-"),
|
||||
layer.name,
|
||||
text.as_deref().unwrap_or("-")
|
||||
);
|
||||
}
|
||||
PcbItem::Field(PcbField {
|
||||
name,
|
||||
visible,
|
||||
text,
|
||||
}) => {
|
||||
println!(
|
||||
" field name={} visible={} text={}",
|
||||
name,
|
||||
visible,
|
||||
text.as_deref().unwrap_or("-")
|
||||
);
|
||||
}
|
||||
PcbItem::Zone(PcbZone {
|
||||
id,
|
||||
name,
|
||||
zone_type,
|
||||
layer_count,
|
||||
filled,
|
||||
polygon_count,
|
||||
..
|
||||
}) => {
|
||||
println!(
|
||||
" zone id={} name={} zone_type={:?} layers={} filled={} polygons={}",
|
||||
id.as_deref().unwrap_or("-"),
|
||||
name,
|
||||
zone_type,
|
||||
layer_count,
|
||||
filled,
|
||||
polygon_count
|
||||
);
|
||||
}
|
||||
PcbItem::Dimension(PcbDimension {
|
||||
id,
|
||||
layer,
|
||||
text,
|
||||
style_kind,
|
||||
..
|
||||
}) => {
|
||||
println!(
|
||||
" dimension id={} layer={} style={} text={}",
|
||||
id.as_deref().unwrap_or("-"),
|
||||
layer.name,
|
||||
style_kind.as_deref().unwrap_or("-"),
|
||||
text.as_deref().unwrap_or("-")
|
||||
);
|
||||
}
|
||||
PcbItem::Group(PcbGroup {
|
||||
id,
|
||||
name,
|
||||
item_count,
|
||||
..
|
||||
}) => {
|
||||
println!(
|
||||
" group id={} name={} item_count={}",
|
||||
id.as_deref().unwrap_or("-"),
|
||||
name,
|
||||
item_count
|
||||
);
|
||||
}
|
||||
PcbItem::Unknown(v) => {
|
||||
println!(" unknown type={} raw_len={}", v.type_url, v.raw_len);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "blocking")]
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let client = KiCadClientBlocking::connect()?;
|
||||
|
||||
retry("ping", || client.ping())?;
|
||||
let version = retry("get_version", || client.get_version())?;
|
||||
println!("version={}", version.full_version);
|
||||
|
||||
let summary = retry("get_selection_summary", || {
|
||||
client.get_selection_summary(Vec::new())
|
||||
})?;
|
||||
println!("selection_total={}", summary.total_items);
|
||||
for count in summary.type_url_counts {
|
||||
println!(
|
||||
"selection_type type_url={} count={}",
|
||||
count.type_url, count.count
|
||||
);
|
||||
}
|
||||
if summary.total_items == 0 {
|
||||
println!("selection is empty; nothing to inspect");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let selected_items = retry("get_selection", || client.get_selection(Vec::new()))?;
|
||||
let selected_details = retry("get_selection_details", || {
|
||||
client.get_selection_details(Vec::new())
|
||||
})?;
|
||||
let selected_raw = retry("get_selection_raw", || client.get_selection_raw(Vec::new()))?;
|
||||
|
||||
println!("selected_items_decoded={}", selected_items.len());
|
||||
for item in &selected_items {
|
||||
print_item(item);
|
||||
}
|
||||
|
||||
println!("selected_items_raw={}", selected_raw.len());
|
||||
for (idx, item) in selected_raw.iter().enumerate() {
|
||||
println!(
|
||||
" raw[{idx}] type_url={} raw_len={}",
|
||||
item.type_url,
|
||||
item.value.len()
|
||||
);
|
||||
}
|
||||
|
||||
println!("selected_items_detail_rows={}", selected_details.len());
|
||||
for (idx, row) in selected_details.iter().enumerate() {
|
||||
println!(
|
||||
" detail[{idx}] type_url={} raw_len={} detail={}",
|
||||
row.type_url, row.raw_len, row.detail
|
||||
);
|
||||
}
|
||||
|
||||
let selected_ids: Vec<String> = selected_items
|
||||
.iter()
|
||||
.filter_map(item_id)
|
||||
.map(str::to_string)
|
||||
.collect();
|
||||
println!("selected_ids={}", selected_ids.len());
|
||||
for id in &selected_ids {
|
||||
println!(" id={id}");
|
||||
}
|
||||
|
||||
if !selected_ids.is_empty() {
|
||||
let bboxes = retry("get_item_bounding_boxes", || {
|
||||
client.get_item_bounding_boxes(selected_ids.clone(), true)
|
||||
})?;
|
||||
println!("item_bounding_boxes={}", bboxes.len());
|
||||
for bbox in bboxes {
|
||||
println!(
|
||||
" bbox id={} x_nm={} y_nm={} width_nm={} height_nm={}",
|
||||
bbox.item_id, bbox.x_nm, bbox.y_nm, bbox.width_nm, bbox.height_nm
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let mut selected_footprints = Vec::new();
|
||||
for item in &selected_items {
|
||||
if let PcbItem::Footprint(fp) = item {
|
||||
selected_footprints.push(fp.clone());
|
||||
}
|
||||
}
|
||||
let selected_refs: BTreeSet<String> = selected_footprints
|
||||
.iter()
|
||||
.filter_map(|fp| fp.reference.clone())
|
||||
.collect();
|
||||
let selected_fp_ids: BTreeSet<String> = selected_footprints
|
||||
.iter()
|
||||
.filter_map(|fp| fp.id.clone())
|
||||
.collect();
|
||||
|
||||
println!("selected_footprints={}", selected_footprints.len());
|
||||
for fp in &selected_footprints {
|
||||
println!(
|
||||
" footprint ref={} id={} pos={:?} orientation_deg={:?} layer={} pad_count={}",
|
||||
fp.reference.as_deref().unwrap_or("-"),
|
||||
fp.id.as_deref().unwrap_or("-"),
|
||||
fp.position_nm.map(|v| (v.x_nm, v.y_nm)),
|
||||
fp.orientation_deg,
|
||||
fp.layer.name,
|
||||
fp.pad_count
|
||||
);
|
||||
}
|
||||
|
||||
match retry("get_selection_as_string", || {
|
||||
client.get_selection_as_string()
|
||||
}) {
|
||||
Ok(selection_dump) => {
|
||||
println!("selection_string_ids={}", selection_dump.ids.len());
|
||||
let ref_values = parse_ref_values_from_selection_sexpr(&selection_dump.contents);
|
||||
println!("ref_value_pairs={}", ref_values.len());
|
||||
for (reference, value) in ref_values {
|
||||
println!(" {reference} => {value}");
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
println!("ref_value_pairs unavailable: {err}");
|
||||
}
|
||||
}
|
||||
|
||||
let pad_netlist = retry("get_pad_netlist", || client.get_pad_netlist())?;
|
||||
let mut selected_pad_rows = Vec::new();
|
||||
for row in pad_netlist {
|
||||
let by_ref = row
|
||||
.footprint_reference
|
||||
.as_ref()
|
||||
.map(|r| selected_refs.contains(r))
|
||||
.unwrap_or(false);
|
||||
let by_id = row
|
||||
.footprint_id
|
||||
.as_ref()
|
||||
.map(|id| selected_fp_ids.contains(id))
|
||||
.unwrap_or(false);
|
||||
if by_ref || by_id {
|
||||
selected_pad_rows.push(row);
|
||||
}
|
||||
}
|
||||
|
||||
println!("selected_pad_rows={}", selected_pad_rows.len());
|
||||
let mut selected_net_names = BTreeSet::new();
|
||||
let mut net_to_selected_refs: BTreeMap<String, BTreeSet<String>> = BTreeMap::new();
|
||||
for row in &selected_pad_rows {
|
||||
let reference = row
|
||||
.footprint_reference
|
||||
.as_deref()
|
||||
.map(str::to_string)
|
||||
.unwrap_or_else(|| "-".to_string());
|
||||
let net = row
|
||||
.net_name
|
||||
.as_deref()
|
||||
.map(str::to_string)
|
||||
.unwrap_or_else(|| "-".to_string());
|
||||
println!(
|
||||
" pad ref={} fp_id={} pad_id={} pad_number={} net_code={:?} net_name={}",
|
||||
reference,
|
||||
row.footprint_id.as_deref().unwrap_or("-"),
|
||||
row.pad_id.as_deref().unwrap_or("-"),
|
||||
row.pad_number,
|
||||
row.net_code,
|
||||
net
|
||||
);
|
||||
|
||||
if net != "-" {
|
||||
selected_net_names.insert(net.clone());
|
||||
if reference != "-" {
|
||||
net_to_selected_refs
|
||||
.entry(net)
|
||||
.or_default()
|
||||
.insert(reference);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("selected_net_names={}", selected_net_names.len());
|
||||
for net in &selected_net_names {
|
||||
println!(" net={net}");
|
||||
}
|
||||
|
||||
println!("interconnections_among_selected_refs");
|
||||
for (net, refs) in &net_to_selected_refs {
|
||||
if refs.len() >= 2 {
|
||||
println!(
|
||||
" net={} refs={}",
|
||||
net,
|
||||
refs.iter().cloned().collect::<Vec<_>>().join(",")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let all_nets = retry("get_nets", || client.get_nets())?;
|
||||
let mut name_to_code = BTreeMap::new();
|
||||
for net in all_nets {
|
||||
name_to_code.insert(net.name, net.code);
|
||||
}
|
||||
|
||||
let mut selected_net_codes = Vec::new();
|
||||
for name in &selected_net_names {
|
||||
if let Some(code) = name_to_code.get(name) {
|
||||
selected_net_codes.push(*code);
|
||||
}
|
||||
}
|
||||
selected_net_codes.sort_unstable();
|
||||
selected_net_codes.dedup();
|
||||
println!("selected_net_codes={:?}", selected_net_codes);
|
||||
|
||||
let route_type_codes: Vec<i32> = [
|
||||
"KOT_PCB_TRACE",
|
||||
"KOT_PCB_VIA",
|
||||
"KOT_PCB_ARC",
|
||||
"KOT_PCB_ZONE",
|
||||
"KOT_PCB_PAD",
|
||||
"KOT_PCB_SHAPE",
|
||||
]
|
||||
.into_iter()
|
||||
.filter_map(pcb_type_code)
|
||||
.collect();
|
||||
println!("route_type_codes={route_type_codes:?}");
|
||||
|
||||
if !selected_net_codes.is_empty() && !route_type_codes.is_empty() {
|
||||
match retry("get_items_by_net", || {
|
||||
client.get_items_by_net(route_type_codes.clone(), selected_net_codes.clone())
|
||||
}) {
|
||||
Ok(connected_items) => {
|
||||
let mut counts: BTreeMap<&'static str, usize> = BTreeMap::new();
|
||||
for item in &connected_items {
|
||||
let label = match item {
|
||||
PcbItem::Track(_) => "track",
|
||||
PcbItem::Via(_) => "via",
|
||||
PcbItem::Arc(_) => "arc",
|
||||
PcbItem::Zone(_) => "zone",
|
||||
PcbItem::Pad(_) => "pad",
|
||||
_ => "other",
|
||||
};
|
||||
*counts.entry(label).or_insert(0) += 1;
|
||||
}
|
||||
|
||||
println!("connected_items_on_selected_nets={}", connected_items.len());
|
||||
for (label, count) in counts {
|
||||
println!(" connected_{label}s={count}");
|
||||
}
|
||||
|
||||
for item in connected_items.iter().take(80) {
|
||||
print_item(item);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
println!("connected_items_on_selected_nets unavailable via GetItemsByNet: {err}");
|
||||
println!("connected_items fallback: scan-by-type + local net-name filter");
|
||||
|
||||
let mut items = Vec::new();
|
||||
for code in &route_type_codes {
|
||||
match retry("get_items_by_type_code_fallback", || {
|
||||
client.get_items_by_type_codes(vec![*code])
|
||||
}) {
|
||||
Ok(mut bucket) => items.append(&mut bucket),
|
||||
Err(type_err) => {
|
||||
println!(" fallback type_code={code} unavailable: {type_err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if items.is_empty() {
|
||||
println!("connected_items fallback unavailable: no items from type scans");
|
||||
} else {
|
||||
let filtered: Vec<PcbItem> = items
|
||||
.into_iter()
|
||||
.filter(|item| match item {
|
||||
PcbItem::Track(track) => track
|
||||
.net
|
||||
.as_ref()
|
||||
.map(|n| selected_net_names.contains(&n.name))
|
||||
.unwrap_or(false),
|
||||
PcbItem::Via(via) => via
|
||||
.net
|
||||
.as_ref()
|
||||
.map(|n| selected_net_names.contains(&n.name))
|
||||
.unwrap_or(false),
|
||||
PcbItem::Arc(arc) => arc
|
||||
.net
|
||||
.as_ref()
|
||||
.map(|n| selected_net_names.contains(&n.name))
|
||||
.unwrap_or(false),
|
||||
PcbItem::Pad(pad) => pad
|
||||
.net
|
||||
.as_ref()
|
||||
.map(|n| selected_net_names.contains(&n.name))
|
||||
.unwrap_or(false),
|
||||
PcbItem::BoardGraphicShape(shape) => shape
|
||||
.net
|
||||
.as_ref()
|
||||
.map(|n| selected_net_names.contains(&n.name))
|
||||
.unwrap_or(false),
|
||||
_ => false,
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut counts: BTreeMap<&'static str, usize> = BTreeMap::new();
|
||||
for item in &filtered {
|
||||
let label = match item {
|
||||
PcbItem::Track(_) => "track",
|
||||
PcbItem::Via(_) => "via",
|
||||
PcbItem::Arc(_) => "arc",
|
||||
PcbItem::Pad(_) => "pad",
|
||||
PcbItem::BoardGraphicShape(_) => "shape",
|
||||
_ => "other",
|
||||
};
|
||||
*counts.entry(label).or_insert(0) += 1;
|
||||
}
|
||||
|
||||
println!(
|
||||
"connected_items_on_selected_nets_fallback={}",
|
||||
filtered.len()
|
||||
);
|
||||
for (label, count) in counts {
|
||||
println!(" connected_{label}s={count}");
|
||||
}
|
||||
|
||||
for item in filtered.iter().take(120) {
|
||||
print_item(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!("connected_items_on_selected_nets unavailable: no resolvable net codes");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "blocking"))]
|
||||
fn main() {
|
||||
eprintln!("run with --features blocking");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
|
@ -430,16 +430,16 @@ impl KiCadClientBlocking {
|
|||
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 get_selection_summary(&self, type_codes: Vec<i32>) -> Result<SelectionSummary, KiCadError>;
|
||||
fn get_selection_raw(&self, type_codes: Vec<i32>) -> Result<Vec<Any>, KiCadError>;
|
||||
fn get_selection_details(&self, type_codes: Vec<i32>) -> Result<Vec<SelectionItemDetail>, KiCadError>;
|
||||
fn get_selection(&self, type_codes: Vec<i32>) -> 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 add_to_selection(&self, item_ids: Vec<String>) -> Result<SelectionMutationResult, KiCadError>;
|
||||
fn clear_selection_raw(&self) -> Result<Vec<Any>, KiCadError>;
|
||||
fn clear_selection(&self) -> Result<SelectionSummary, KiCadError>;
|
||||
fn clear_selection(&self) -> Result<SelectionMutationResult, 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 remove_from_selection(&self, item_ids: Vec<String>) -> Result<SelectionMutationResult, KiCadError>;
|
||||
fn get_pad_netlist(&self) -> Result<Vec<PadNetEntry>, KiCadError>;
|
||||
fn get_vias_raw(&self) -> Result<Vec<Any>, KiCadError>;
|
||||
fn get_vias(&self) -> Result<Vec<PcbVia>, KiCadError>;
|
||||
|
|
@ -477,7 +477,7 @@ impl KiCadClientBlocking {
|
|||
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_selection_as_string(&self) -> Result<SelectionStringDump, 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>;
|
||||
|
|
|
|||
666
src/client.rs
666
src/client.rs
|
|
@ -5,25 +5,8 @@ use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
|||
|
||||
use crate::envelope;
|
||||
use crate::error::KiCadError;
|
||||
use crate::model::board::{
|
||||
ArcStartMidEndNm, BoardEditorAppearanceSettings, BoardEnabledLayers, BoardFlipMode,
|
||||
BoardLayerClass, BoardLayerGraphicsDefault, BoardLayerInfo, BoardNet, BoardOriginKind,
|
||||
BoardStackup, BoardStackupDielectricProperties, BoardStackupLayer, BoardStackupLayerType,
|
||||
ColorRgba, DrcSeverity, GraphicsDefaults, InactiveLayerDisplayMode, NetClassBoardSettings,
|
||||
NetClassForNetEntry, NetClassInfo, NetClassType, NetColorDisplayMode, PadNetEntry,
|
||||
PadShapeAsPolygonEntry, PadstackPresenceEntry, PadstackPresenceState, PcbArc,
|
||||
PcbBoardGraphicShape, PcbBoardText, PcbBoardTextBox, PcbDimension, PcbField, PcbFootprint,
|
||||
PcbGroup, PcbItem, PcbPad, PcbPadType, PcbTrack, PcbUnknownItem, PcbVia, PcbViaLayers,
|
||||
PcbViaType, PcbZone, PcbZoneType, PolyLineNm, PolyLineNodeGeometryNm, PolygonWithHolesNm,
|
||||
RatsnestDisplayMode, Vector2Nm,
|
||||
};
|
||||
use crate::model::common::{
|
||||
CommitAction, CommitSession, DocumentSpecifier, DocumentType, EditorFrameType, ItemBoundingBox,
|
||||
ItemHitTestResult, MapMergeMode, PcbObjectTypeCode, ProjectInfo, RunActionStatus,
|
||||
SelectionItemDetail, SelectionSummary, SelectionTypeCount, TextAsShapesEntry,
|
||||
TextAttributesSpec, TextBoxSpec, TextExtents, TextHorizontalAlignment, TextObjectSpec,
|
||||
TextShape, TextShapeGeometry, TextSpec, TextVerticalAlignment, TitleBlockInfo, VersionInfo,
|
||||
};
|
||||
use crate::model::board::*;
|
||||
use crate::model::common::*;
|
||||
use crate::proto::kiapi::board as board_proto;
|
||||
use crate::proto::kiapi::board::commands as board_commands;
|
||||
use crate::proto::kiapi::board::types as board_types;
|
||||
|
|
@ -1043,7 +1026,10 @@ impl KiCadClient {
|
|||
}
|
||||
|
||||
/// Returns a compact summary of the current PCB selection.
|
||||
pub async fn get_selection_summary(&self) -> Result<SelectionSummary, KiCadError> {
|
||||
pub async fn get_selection_summary(
|
||||
&self,
|
||||
type_codes: Vec<i32>,
|
||||
) -> Result<SelectionSummary, KiCadError> {
|
||||
let document = self.current_board_document_proto().await?;
|
||||
let command = common_commands::GetSelection {
|
||||
header: Some(common_types::ItemHeader {
|
||||
|
|
@ -1051,7 +1037,7 @@ impl KiCadClient {
|
|||
container: None,
|
||||
field_mask: None,
|
||||
}),
|
||||
types: Vec::new(),
|
||||
types: type_codes,
|
||||
};
|
||||
|
||||
let response = self
|
||||
|
|
@ -1061,13 +1047,16 @@ impl KiCadClient {
|
|||
let payload: common_commands::SelectionResponse =
|
||||
envelope::unpack_any(&response, RES_SELECTION_RESPONSE)?;
|
||||
|
||||
Ok(summarize_selection(payload.items))
|
||||
Ok(summarize_selection(&payload.items))
|
||||
}
|
||||
|
||||
pub async fn get_selection_raw(&self) -> Result<Vec<prost_types::Any>, KiCadError> {
|
||||
pub async fn get_selection_raw(
|
||||
&self,
|
||||
type_codes: Vec<i32>,
|
||||
) -> Result<Vec<prost_types::Any>, KiCadError> {
|
||||
let command = common_commands::GetSelection {
|
||||
header: Some(self.current_board_item_header().await?),
|
||||
types: Vec::new(),
|
||||
types: type_codes,
|
||||
};
|
||||
|
||||
let response = self
|
||||
|
|
@ -1080,14 +1069,17 @@ impl KiCadClient {
|
|||
Ok(payload.items)
|
||||
}
|
||||
|
||||
pub async fn get_selection_details(&self) -> Result<Vec<SelectionItemDetail>, KiCadError> {
|
||||
let items = self.get_selection_raw().await?;
|
||||
pub async fn get_selection_details(
|
||||
&self,
|
||||
type_codes: Vec<i32>,
|
||||
) -> Result<Vec<SelectionItemDetail>, KiCadError> {
|
||||
let items = self.get_selection_raw(type_codes).await?;
|
||||
summarize_item_details(items)
|
||||
}
|
||||
|
||||
/// Returns the current selection as decoded typed PCB items.
|
||||
pub async fn get_selection(&self) -> Result<Vec<PcbItem>, KiCadError> {
|
||||
let items = self.get_selection_raw().await?;
|
||||
pub async fn get_selection(&self, type_codes: Vec<i32>) -> Result<Vec<PcbItem>, KiCadError> {
|
||||
let items = self.get_selection_raw(type_codes).await?;
|
||||
decode_pcb_items(items)
|
||||
}
|
||||
|
||||
|
|
@ -1123,9 +1115,11 @@ impl KiCadClient {
|
|||
pub async fn add_to_selection(
|
||||
&self,
|
||||
item_ids: Vec<String>,
|
||||
) -> Result<SelectionSummary, KiCadError> {
|
||||
let items = self.add_to_selection_raw(item_ids).await?;
|
||||
Ok(summarize_selection(items))
|
||||
) -> Result<SelectionMutationResult, KiCadError> {
|
||||
let raw_items = self.add_to_selection_raw(item_ids).await?;
|
||||
let summary = summarize_selection(&raw_items);
|
||||
let items = decode_pcb_items(raw_items)?;
|
||||
Ok(SelectionMutationResult { items, summary })
|
||||
}
|
||||
|
||||
pub async fn clear_selection_raw(&self) -> Result<Vec<prost_types::Any>, KiCadError> {
|
||||
|
|
@ -1150,9 +1144,11 @@ impl KiCadClient {
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn clear_selection(&self) -> Result<SelectionSummary, KiCadError> {
|
||||
let items = self.clear_selection_raw().await?;
|
||||
Ok(summarize_selection(items))
|
||||
pub async fn clear_selection(&self) -> Result<SelectionMutationResult, KiCadError> {
|
||||
let raw_items = self.clear_selection_raw().await?;
|
||||
let summary = summarize_selection(&raw_items);
|
||||
let items = decode_pcb_items(raw_items)?;
|
||||
Ok(SelectionMutationResult { items, summary })
|
||||
}
|
||||
|
||||
pub async fn remove_from_selection_raw(
|
||||
|
|
@ -1187,9 +1183,11 @@ impl KiCadClient {
|
|||
pub async fn remove_from_selection(
|
||||
&self,
|
||||
item_ids: Vec<String>,
|
||||
) -> Result<SelectionSummary, KiCadError> {
|
||||
let items = self.remove_from_selection_raw(item_ids).await?;
|
||||
Ok(summarize_selection(items))
|
||||
) -> Result<SelectionMutationResult, KiCadError> {
|
||||
let raw_items = self.remove_from_selection_raw(item_ids).await?;
|
||||
let summary = summarize_selection(&raw_items);
|
||||
let items = decode_pcb_items(raw_items)?;
|
||||
Ok(SelectionMutationResult { items, summary })
|
||||
}
|
||||
|
||||
pub async fn get_pad_netlist(&self) -> Result<Vec<PadNetEntry>, KiCadError> {
|
||||
|
|
@ -1846,7 +1844,7 @@ impl KiCadClient {
|
|||
}
|
||||
|
||||
/// Serializes current selection to KiCad's string format.
|
||||
pub async fn get_selection_as_string(&self) -> Result<String, KiCadError> {
|
||||
pub async fn get_selection_as_string(&self) -> Result<SelectionStringDump, KiCadError> {
|
||||
let command = common_commands::SaveSelectionToString {};
|
||||
|
||||
let response = self
|
||||
|
|
@ -1854,7 +1852,10 @@ impl KiCadClient {
|
|||
.await?;
|
||||
let payload: common_commands::SavedSelectionResponse =
|
||||
envelope::unpack_any(&response, RES_SAVED_SELECTION_RESPONSE)?;
|
||||
Ok(payload.contents)
|
||||
Ok(SelectionStringDump {
|
||||
ids: payload.ids.into_iter().map(|id| id.value).collect(),
|
||||
contents: payload.contents,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn get_items_by_id_raw(
|
||||
|
|
@ -2381,10 +2382,10 @@ fn map_merge_mode_to_proto(value: MapMergeMode) -> i32 {
|
|||
}
|
||||
}
|
||||
|
||||
fn summarize_selection(items: Vec<prost_types::Any>) -> SelectionSummary {
|
||||
fn summarize_selection(items: &[prost_types::Any]) -> SelectionSummary {
|
||||
let mut counts = BTreeMap::<String, usize>::new();
|
||||
|
||||
for item in &items {
|
||||
for item in items {
|
||||
let entry = counts.entry(item.type_url.clone()).or_insert(0);
|
||||
*entry += 1;
|
||||
}
|
||||
|
|
@ -3062,10 +3063,71 @@ fn map_via_type(value: i32) -> PcbViaType {
|
|||
}
|
||||
}
|
||||
|
||||
fn map_via_layers(pad_stack: Option<board_types::PadStack>) -> Option<PcbViaLayers> {
|
||||
fn map_lock_state(value: i32) -> ItemLockState {
|
||||
match common_types::LockedState::try_from(value) {
|
||||
Ok(common_types::LockedState::LsUnlocked) => ItemLockState::Unlocked,
|
||||
Ok(common_types::LockedState::LsLocked) => ItemLockState::Locked,
|
||||
_ => ItemLockState::Unknown(value),
|
||||
}
|
||||
}
|
||||
|
||||
fn map_padstack_drill(drill: board_types::DrillProperties) -> PcbPadstackDrill {
|
||||
let shape = board_types::DrillShape::try_from(drill.shape)
|
||||
.map(|value| value.as_str_name().to_string())
|
||||
.unwrap_or_else(|_| format!("UNKNOWN({})", drill.shape));
|
||||
let capped = board_types::ViaDrillCappingMode::try_from(drill.capped)
|
||||
.map(|value| value.as_str_name().to_string())
|
||||
.unwrap_or_else(|_| format!("UNKNOWN({})", drill.capped));
|
||||
let filled = board_types::ViaDrillFillingMode::try_from(drill.filled)
|
||||
.map(|value| value.as_str_name().to_string())
|
||||
.unwrap_or_else(|_| format!("UNKNOWN({})", drill.filled));
|
||||
|
||||
PcbPadstackDrill {
|
||||
start_layer: layer_to_model(drill.start_layer),
|
||||
end_layer: layer_to_model(drill.end_layer),
|
||||
diameter_nm: drill.diameter.map(map_vector2_nm),
|
||||
shape: Some(shape),
|
||||
capped: Some(capped),
|
||||
filled: Some(filled),
|
||||
}
|
||||
}
|
||||
|
||||
fn map_pad_stack(pad_stack: Option<&board_types::PadStack>) -> Option<PcbPadStack> {
|
||||
let pad_stack = pad_stack?;
|
||||
|
||||
let (drill_start_layer, drill_end_layer) = if let Some(drill) = pad_stack.drill {
|
||||
let stack_type = board_types::PadStackType::try_from(pad_stack.r#type)
|
||||
.map(|value| value.as_str_name().to_string())
|
||||
.unwrap_or_else(|_| format!("UNKNOWN({})", pad_stack.r#type));
|
||||
let unconnected_layer_removal =
|
||||
board_types::UnconnectedLayerRemoval::try_from(pad_stack.unconnected_layer_removal)
|
||||
.map(|value| value.as_str_name().to_string())
|
||||
.unwrap_or_else(|_| format!("UNKNOWN({})", pad_stack.unconnected_layer_removal));
|
||||
|
||||
Some(PcbPadStack {
|
||||
stack_type: Some(stack_type),
|
||||
layers: pad_stack
|
||||
.layers
|
||||
.iter()
|
||||
.copied()
|
||||
.map(layer_to_model)
|
||||
.collect(),
|
||||
drill: pad_stack.drill.map(map_padstack_drill),
|
||||
unconnected_layer_removal: Some(unconnected_layer_removal),
|
||||
copper_layer_count: pad_stack.copper_layers.len(),
|
||||
has_front_outer_layers: pad_stack.front_outer_layers.is_some(),
|
||||
has_back_outer_layers: pad_stack.back_outer_layers.is_some(),
|
||||
has_zone_settings: pad_stack.zone_settings.is_some(),
|
||||
secondary_drill: pad_stack.secondary_drill.map(map_padstack_drill),
|
||||
tertiary_drill: pad_stack.tertiary_drill.map(map_padstack_drill),
|
||||
has_front_post_machining: pad_stack.front_post_machining.is_some(),
|
||||
has_back_post_machining: pad_stack.back_post_machining.is_some(),
|
||||
})
|
||||
}
|
||||
|
||||
fn map_via_layers(pad_stack: Option<&board_types::PadStack>) -> Option<PcbViaLayers> {
|
||||
let pad_stack = pad_stack?;
|
||||
|
||||
let (drill_start_layer, drill_end_layer) = if let Some(drill) = pad_stack.drill.as_ref() {
|
||||
(
|
||||
Some(layer_to_model(drill.start_layer)),
|
||||
Some(layer_to_model(drill.end_layer)),
|
||||
|
|
@ -3075,12 +3137,151 @@ fn map_via_layers(pad_stack: Option<board_types::PadStack>) -> Option<PcbViaLaye
|
|||
};
|
||||
|
||||
Some(PcbViaLayers {
|
||||
padstack_layers: pad_stack.layers.into_iter().map(layer_to_model).collect(),
|
||||
padstack_layers: pad_stack
|
||||
.layers
|
||||
.iter()
|
||||
.copied()
|
||||
.map(layer_to_model)
|
||||
.collect(),
|
||||
drill_start_layer,
|
||||
drill_end_layer,
|
||||
})
|
||||
}
|
||||
|
||||
fn map_text_attributes(
|
||||
attributes: Option<common_types::TextAttributes>,
|
||||
) -> Option<PcbTextAttributes> {
|
||||
let attributes = attributes?;
|
||||
let font_name = (!attributes.font_name.is_empty()).then_some(attributes.font_name);
|
||||
let horizontal_alignment =
|
||||
common_types::HorizontalAlignment::try_from(attributes.horizontal_alignment)
|
||||
.map(|value| value.as_str_name().to_string())
|
||||
.ok();
|
||||
let vertical_alignment =
|
||||
common_types::VerticalAlignment::try_from(attributes.vertical_alignment)
|
||||
.map(|value| value.as_str_name().to_string())
|
||||
.ok();
|
||||
|
||||
Some(PcbTextAttributes {
|
||||
font_name,
|
||||
horizontal_alignment,
|
||||
vertical_alignment,
|
||||
stroke_width_nm: map_optional_distance_nm(attributes.stroke_width),
|
||||
italic: attributes.italic,
|
||||
bold: attributes.bold,
|
||||
underlined: attributes.underlined,
|
||||
mirrored: attributes.mirrored,
|
||||
multiline: attributes.multiline,
|
||||
keep_upright: attributes.keep_upright,
|
||||
size_nm: attributes.size.map(map_vector2_nm),
|
||||
})
|
||||
}
|
||||
|
||||
fn map_graphic_shape_geometry(
|
||||
shape: Option<&common_types::GraphicShape>,
|
||||
) -> Option<PcbGraphicShapeGeometry> {
|
||||
let geometry = shape?.geometry.as_ref()?;
|
||||
match geometry {
|
||||
common_types::graphic_shape::Geometry::Segment(segment) => {
|
||||
Some(PcbGraphicShapeGeometry::Segment {
|
||||
start_nm: segment.start.map(map_vector2_nm),
|
||||
end_nm: segment.end.map(map_vector2_nm),
|
||||
})
|
||||
}
|
||||
common_types::graphic_shape::Geometry::Rectangle(rect) => {
|
||||
Some(PcbGraphicShapeGeometry::Rectangle {
|
||||
top_left_nm: rect.top_left.map(map_vector2_nm),
|
||||
bottom_right_nm: rect.bottom_right.map(map_vector2_nm),
|
||||
corner_radius_nm: map_optional_distance_nm(rect.corner_radius),
|
||||
})
|
||||
}
|
||||
common_types::graphic_shape::Geometry::Arc(arc) => Some(PcbGraphicShapeGeometry::Arc {
|
||||
start_nm: arc.start.map(map_vector2_nm),
|
||||
mid_nm: arc.mid.map(map_vector2_nm),
|
||||
end_nm: arc.end.map(map_vector2_nm),
|
||||
}),
|
||||
common_types::graphic_shape::Geometry::Circle(circle) => {
|
||||
Some(PcbGraphicShapeGeometry::Circle {
|
||||
center_nm: circle.center.map(map_vector2_nm),
|
||||
radius_point_nm: circle.radius_point.map(map_vector2_nm),
|
||||
})
|
||||
}
|
||||
common_types::graphic_shape::Geometry::Polygon(polyset) => {
|
||||
Some(PcbGraphicShapeGeometry::Polygon {
|
||||
polygon_count: polyset.polygons.len(),
|
||||
})
|
||||
}
|
||||
common_types::graphic_shape::Geometry::Bezier(bezier) => {
|
||||
Some(PcbGraphicShapeGeometry::Bezier {
|
||||
start_nm: bezier.start.map(map_vector2_nm),
|
||||
control1_nm: bezier.control1.map(map_vector2_nm),
|
||||
control2_nm: bezier.control2.map(map_vector2_nm),
|
||||
end_nm: bezier.end.map(map_vector2_nm),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn map_graphic_shape_kind(shape: Option<&common_types::GraphicShape>) -> Option<String> {
|
||||
let geometry = shape?.geometry.as_ref()?;
|
||||
Some(match geometry {
|
||||
common_types::graphic_shape::Geometry::Segment(_) => "SEGMENT".to_string(),
|
||||
common_types::graphic_shape::Geometry::Rectangle(_) => "RECTANGLE".to_string(),
|
||||
common_types::graphic_shape::Geometry::Arc(_) => "ARC".to_string(),
|
||||
common_types::graphic_shape::Geometry::Circle(_) => "CIRCLE".to_string(),
|
||||
common_types::graphic_shape::Geometry::Polygon(_) => "POLYGON".to_string(),
|
||||
common_types::graphic_shape::Geometry::Bezier(_) => "BEZIER".to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
fn map_dimension_style(
|
||||
style: Option<board_types::dimension::DimensionStyle>,
|
||||
) -> Option<PcbDimensionStyle> {
|
||||
let style = style?;
|
||||
match style {
|
||||
board_types::dimension::DimensionStyle::Aligned(aligned) => {
|
||||
Some(PcbDimensionStyle::Aligned {
|
||||
start_nm: aligned.start.map(map_vector2_nm),
|
||||
end_nm: aligned.end.map(map_vector2_nm),
|
||||
height_nm: map_optional_distance_nm(aligned.height),
|
||||
extension_height_nm: map_optional_distance_nm(aligned.extension_height),
|
||||
})
|
||||
}
|
||||
board_types::dimension::DimensionStyle::Orthogonal(orthogonal) => {
|
||||
let alignment = common_types::AxisAlignment::try_from(orthogonal.alignment)
|
||||
.map(|value| value.as_str_name().to_string())
|
||||
.unwrap_or_else(|_| format!("UNKNOWN({})", orthogonal.alignment));
|
||||
|
||||
Some(PcbDimensionStyle::Orthogonal {
|
||||
start_nm: orthogonal.start.map(map_vector2_nm),
|
||||
end_nm: orthogonal.end.map(map_vector2_nm),
|
||||
height_nm: map_optional_distance_nm(orthogonal.height),
|
||||
extension_height_nm: map_optional_distance_nm(orthogonal.extension_height),
|
||||
alignment: Some(alignment),
|
||||
})
|
||||
}
|
||||
board_types::dimension::DimensionStyle::Radial(radial) => Some(PcbDimensionStyle::Radial {
|
||||
center_nm: radial.center.map(map_vector2_nm),
|
||||
radius_point_nm: radial.radius_point.map(map_vector2_nm),
|
||||
leader_length_nm: map_optional_distance_nm(radial.leader_length),
|
||||
}),
|
||||
board_types::dimension::DimensionStyle::Leader(leader) => {
|
||||
let border_style = board_types::DimensionTextBorderStyle::try_from(leader.border_style)
|
||||
.map(|value| value.as_str_name().to_string())
|
||||
.unwrap_or_else(|_| format!("UNKNOWN({})", leader.border_style));
|
||||
Some(PcbDimensionStyle::Leader {
|
||||
start_nm: leader.start.map(map_vector2_nm),
|
||||
end_nm: leader.end.map(map_vector2_nm),
|
||||
border_style: Some(border_style),
|
||||
})
|
||||
}
|
||||
board_types::dimension::DimensionStyle::Center(center) => Some(PcbDimensionStyle::Center {
|
||||
center_nm: center.center.map(map_vector2_nm),
|
||||
end_nm: center.end.map(map_vector2_nm),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn map_pad_type(value: i32) -> PcbPadType {
|
||||
match board_types::PadType::try_from(value) {
|
||||
Ok(board_types::PadType::PtPth) => PcbPadType::Pth,
|
||||
|
|
@ -3113,6 +3314,7 @@ fn decode_pcb_item(item: prost_types::Any) -> Result<PcbItem, KiCadError> {
|
|||
start_nm: track.start.map(map_vector2_nm),
|
||||
end_nm: track.end.map(map_vector2_nm),
|
||||
width_nm: map_optional_distance_nm(track.width),
|
||||
locked: map_lock_state(track.locked),
|
||||
layer: layer_to_model(track.layer),
|
||||
net: map_optional_net(track.net),
|
||||
}));
|
||||
|
|
@ -3126,6 +3328,7 @@ fn decode_pcb_item(item: prost_types::Any) -> Result<PcbItem, KiCadError> {
|
|||
mid_nm: arc.mid.map(map_vector2_nm),
|
||||
end_nm: arc.end.map(map_vector2_nm),
|
||||
width_nm: map_optional_distance_nm(arc.width),
|
||||
locked: map_lock_state(arc.locked),
|
||||
layer: layer_to_model(arc.layer),
|
||||
net: map_optional_net(arc.net),
|
||||
}));
|
||||
|
|
@ -3137,7 +3340,9 @@ fn decode_pcb_item(item: prost_types::Any) -> Result<PcbItem, KiCadError> {
|
|||
id: via.id.map(|id| id.value),
|
||||
position_nm: via.position.map(map_vector2_nm),
|
||||
via_type: map_via_type(via.r#type),
|
||||
layers: map_via_layers(via.pad_stack),
|
||||
locked: map_lock_state(via.locked),
|
||||
layers: map_via_layers(via.pad_stack.as_ref()),
|
||||
pad_stack: map_pad_stack(via.pad_stack.as_ref()),
|
||||
net: map_optional_net(via.net),
|
||||
}));
|
||||
}
|
||||
|
|
@ -3154,6 +3359,32 @@ fn decode_pcb_item(item: prost_types::Any) -> Result<PcbItem, KiCadError> {
|
|||
.and_then(|board_text| board_text.text.as_ref())
|
||||
.map(|text| text.text.clone())
|
||||
.filter(|value| !value.is_empty());
|
||||
let value = footprint
|
||||
.value_field
|
||||
.as_ref()
|
||||
.and_then(|field| field.text.as_ref())
|
||||
.and_then(|board_text| board_text.text.as_ref())
|
||||
.map(|text| text.text.clone())
|
||||
.filter(|value| !value.is_empty());
|
||||
let datasheet = footprint
|
||||
.datasheet_field
|
||||
.as_ref()
|
||||
.and_then(|field| field.text.as_ref())
|
||||
.and_then(|board_text| board_text.text.as_ref())
|
||||
.map(|text| text.text.clone())
|
||||
.filter(|value| !value.is_empty());
|
||||
let description = footprint
|
||||
.description_field
|
||||
.as_ref()
|
||||
.and_then(|field| field.text.as_ref())
|
||||
.and_then(|board_text| board_text.text.as_ref())
|
||||
.map(|text| text.text.clone())
|
||||
.filter(|value| !value.is_empty());
|
||||
let definition_item_count = footprint
|
||||
.definition
|
||||
.as_ref()
|
||||
.map(|definition| definition.items.len())
|
||||
.unwrap_or(0);
|
||||
let pad_count = footprint
|
||||
.definition
|
||||
.as_ref()
|
||||
|
|
@ -3165,6 +3396,27 @@ fn decode_pcb_item(item: prost_types::Any) -> Result<PcbItem, KiCadError> {
|
|||
.count()
|
||||
})
|
||||
.unwrap_or(0);
|
||||
let symbol_sheet_name = (!footprint.symbol_sheet_name.is_empty())
|
||||
.then_some(footprint.symbol_sheet_name.clone());
|
||||
let symbol_sheet_filename = (!footprint.symbol_sheet_filename.is_empty())
|
||||
.then_some(footprint.symbol_sheet_filename.clone());
|
||||
let symbol_footprint_filters = (!footprint.symbol_footprint_filters.is_empty())
|
||||
.then_some(footprint.symbol_footprint_filters.clone());
|
||||
let has_symbol_path = footprint.symbol_path.is_some();
|
||||
let symbol_link = if has_symbol_path
|
||||
|| symbol_sheet_name.is_some()
|
||||
|| symbol_sheet_filename.is_some()
|
||||
|| symbol_footprint_filters.is_some()
|
||||
{
|
||||
Some(PcbFootprintSymbolLink {
|
||||
has_symbol_path,
|
||||
sheet_name: symbol_sheet_name,
|
||||
sheet_filename: symbol_sheet_filename,
|
||||
footprint_filters: symbol_footprint_filters,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
return Ok(PcbItem::Footprint(PcbFootprint {
|
||||
id: footprint.id.map(|id| id.value),
|
||||
|
|
@ -3172,17 +3424,42 @@ fn decode_pcb_item(item: prost_types::Any) -> Result<PcbItem, KiCadError> {
|
|||
position_nm: footprint.position.map(map_vector2_nm),
|
||||
orientation_deg: footprint.orientation.map(|angle| angle.value_degrees),
|
||||
layer: layer_to_model(footprint.layer),
|
||||
locked: map_lock_state(footprint.locked),
|
||||
value,
|
||||
datasheet,
|
||||
description,
|
||||
has_attributes: footprint.attributes.is_some(),
|
||||
has_overrides: footprint.overrides.is_some(),
|
||||
has_definition: footprint.definition.is_some(),
|
||||
definition_item_count,
|
||||
symbol_link,
|
||||
pad_count,
|
||||
}));
|
||||
}
|
||||
|
||||
if item.type_url == envelope::type_url("kiapi.board.types.Pad") {
|
||||
let pad = decode_any::<board_types::Pad>(&item, "kiapi.board.types.Pad")?;
|
||||
let symbol_pin = pad.symbol_pin.map(|pin| {
|
||||
let pin_type = common_types::ElectricalPinType::try_from(pin.r#type)
|
||||
.map(|value| value.as_str_name().to_string())
|
||||
.unwrap_or_else(|_| format!("UNKNOWN({})", pin.r#type));
|
||||
PcbSymbolPinInfo {
|
||||
name: pin.name,
|
||||
pin_type: Some(pin_type),
|
||||
no_connect: pin.no_connect,
|
||||
}
|
||||
});
|
||||
return Ok(PcbItem::Pad(PcbPad {
|
||||
id: pad.id.map(|id| id.value),
|
||||
locked: map_lock_state(pad.locked),
|
||||
number: pad.number,
|
||||
pad_type: map_pad_type(pad.r#type),
|
||||
position_nm: pad.position.map(map_vector2_nm),
|
||||
pad_stack: map_pad_stack(pad.pad_stack.as_ref()),
|
||||
copper_clearance_override_nm: map_optional_distance_nm(pad.copper_clearance_override),
|
||||
pad_to_die_length_nm: map_optional_distance_nm(pad.pad_to_die_length),
|
||||
pad_to_die_delay_as: pad.pad_to_die_delay.map(|value| value.value_as),
|
||||
symbol_pin,
|
||||
net: map_optional_net(pad.net),
|
||||
}));
|
||||
}
|
||||
|
|
@ -3192,35 +3469,97 @@ fn decode_pcb_item(item: prost_types::Any) -> Result<PcbItem, KiCadError> {
|
|||
&item,
|
||||
"kiapi.board.types.BoardGraphicShape",
|
||||
)?;
|
||||
let geometry_kind = shape
|
||||
let geometry_kind = map_graphic_shape_kind(shape.shape.as_ref());
|
||||
let geometry = map_graphic_shape_geometry(shape.shape.as_ref());
|
||||
let stroke_width_nm = shape
|
||||
.shape
|
||||
.as_ref()
|
||||
.and_then(|graphic| graphic.geometry.as_ref())
|
||||
.map(|value| format!("{value:?}"));
|
||||
.and_then(|graphic| graphic.attributes.as_ref())
|
||||
.and_then(|attrs| attrs.stroke.as_ref())
|
||||
.and_then(|stroke| stroke.width)
|
||||
.map(|width| width.value_nm);
|
||||
let stroke_style = shape
|
||||
.shape
|
||||
.as_ref()
|
||||
.and_then(|graphic| graphic.attributes.as_ref())
|
||||
.and_then(|attrs| attrs.stroke.as_ref())
|
||||
.map(|stroke| {
|
||||
common_types::StrokeLineStyle::try_from(stroke.style)
|
||||
.map(|value| value.as_str_name().to_string())
|
||||
.unwrap_or_else(|_| format!("UNKNOWN({})", stroke.style))
|
||||
});
|
||||
let fill_type = shape
|
||||
.shape
|
||||
.as_ref()
|
||||
.and_then(|graphic| graphic.attributes.as_ref())
|
||||
.and_then(|attrs| attrs.fill.as_ref())
|
||||
.map(|fill| {
|
||||
common_types::GraphicFillType::try_from(fill.fill_type)
|
||||
.map(|value| value.as_str_name().to_string())
|
||||
.unwrap_or_else(|_| format!("UNKNOWN({})", fill.fill_type))
|
||||
});
|
||||
return Ok(PcbItem::BoardGraphicShape(PcbBoardGraphicShape {
|
||||
id: shape.id.map(|id| id.value),
|
||||
layer: layer_to_model(shape.layer),
|
||||
locked: map_lock_state(shape.locked),
|
||||
net: map_optional_net(shape.net),
|
||||
geometry_kind,
|
||||
geometry,
|
||||
stroke_width_nm,
|
||||
stroke_style,
|
||||
fill_type,
|
||||
}));
|
||||
}
|
||||
|
||||
if item.type_url == envelope::type_url("kiapi.board.types.BoardText") {
|
||||
let text = decode_any::<board_types::BoardText>(&item, "kiapi.board.types.BoardText")?;
|
||||
let (body, position_nm, hyperlink, attributes) = if let Some(value) = text.text {
|
||||
let hyperlink = (!value.hyperlink.is_empty()).then_some(value.hyperlink.clone());
|
||||
let body = (!value.text.is_empty()).then_some(value.text.clone());
|
||||
(
|
||||
body,
|
||||
value.position.map(map_vector2_nm),
|
||||
hyperlink,
|
||||
map_text_attributes(value.attributes),
|
||||
)
|
||||
} else {
|
||||
(None, None, None, None)
|
||||
};
|
||||
|
||||
return Ok(PcbItem::BoardText(PcbBoardText {
|
||||
id: text.id.map(|id| id.value),
|
||||
layer: layer_to_model(text.layer),
|
||||
text: text.text.map(|value| value.text),
|
||||
text: body,
|
||||
position_nm,
|
||||
hyperlink,
|
||||
attributes,
|
||||
knockout: text.knockout,
|
||||
locked: map_lock_state(text.locked),
|
||||
}));
|
||||
}
|
||||
|
||||
if item.type_url == envelope::type_url("kiapi.board.types.BoardTextBox") {
|
||||
let textbox =
|
||||
decode_any::<board_types::BoardTextBox>(&item, "kiapi.board.types.BoardTextBox")?;
|
||||
let (body, top_left_nm, bottom_right_nm, attributes) = if let Some(value) = textbox.textbox
|
||||
{
|
||||
(
|
||||
(!value.text.is_empty()).then_some(value.text.clone()),
|
||||
value.top_left.map(map_vector2_nm),
|
||||
value.bottom_right.map(map_vector2_nm),
|
||||
map_text_attributes(value.attributes),
|
||||
)
|
||||
} else {
|
||||
(None, None, None, None)
|
||||
};
|
||||
return Ok(PcbItem::BoardTextBox(PcbBoardTextBox {
|
||||
id: textbox.id.map(|id| id.value),
|
||||
layer: layer_to_model(textbox.layer),
|
||||
text: textbox.textbox.map(|value| value.text),
|
||||
text: body,
|
||||
top_left_nm,
|
||||
bottom_right_nm,
|
||||
attributes,
|
||||
locked: map_lock_state(textbox.locked),
|
||||
}));
|
||||
}
|
||||
|
||||
|
|
@ -3239,23 +3578,109 @@ fn decode_pcb_item(item: prost_types::Any) -> Result<PcbItem, KiCadError> {
|
|||
|
||||
if item.type_url == envelope::type_url("kiapi.board.types.Zone") {
|
||||
let zone = decode_any::<board_types::Zone>(&item, "kiapi.board.types.Zone")?;
|
||||
let has_copper_settings = matches!(
|
||||
zone.settings,
|
||||
Some(board_types::zone::Settings::CopperSettings(_))
|
||||
);
|
||||
let has_rule_area_settings = matches!(
|
||||
zone.settings,
|
||||
Some(board_types::zone::Settings::RuleAreaSettings(_))
|
||||
);
|
||||
let border_style = zone.border.as_ref().map(|border| {
|
||||
board_types::ZoneBorderStyle::try_from(border.style)
|
||||
.map(|value| value.as_str_name().to_string())
|
||||
.unwrap_or_else(|_| format!("UNKNOWN({})", border.style))
|
||||
});
|
||||
let border_pitch_nm = zone
|
||||
.border
|
||||
.as_ref()
|
||||
.and_then(|border| map_optional_distance_nm(border.pitch));
|
||||
let layer_properties = zone
|
||||
.layer_properties
|
||||
.iter()
|
||||
.map(|entry| PcbZoneLayerProperty {
|
||||
layer: layer_to_model(entry.layer),
|
||||
hatching_offset_nm: entry.hatching_offset.map(map_vector2_nm),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let layers = zone
|
||||
.layers
|
||||
.iter()
|
||||
.copied()
|
||||
.map(layer_to_model)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
return Ok(PcbItem::Zone(PcbZone {
|
||||
id: zone.id.map(|id| id.value),
|
||||
name: zone.name,
|
||||
zone_type: map_zone_type(zone.r#type),
|
||||
layers,
|
||||
layer_count: zone.layers.len(),
|
||||
priority: zone.priority,
|
||||
locked: map_lock_state(zone.locked),
|
||||
filled: zone.filled,
|
||||
polygon_count: zone.filled_polygons.len(),
|
||||
outline_polygon_count: zone.outline.map_or(0, |outline| outline.polygons.len()),
|
||||
has_copper_settings,
|
||||
has_rule_area_settings,
|
||||
border_style,
|
||||
border_pitch_nm,
|
||||
layer_properties,
|
||||
}));
|
||||
}
|
||||
|
||||
if item.type_url == envelope::type_url("kiapi.board.types.Dimension") {
|
||||
let dimension = decode_any::<board_types::Dimension>(&item, "kiapi.board.types.Dimension")?;
|
||||
let style_kind = dimension.dimension_style.as_ref().map(|value| match value {
|
||||
board_types::dimension::DimensionStyle::Aligned(_) => "ALIGNED".to_string(),
|
||||
board_types::dimension::DimensionStyle::Orthogonal(_) => "ORTHOGONAL".to_string(),
|
||||
board_types::dimension::DimensionStyle::Radial(_) => "RADIAL".to_string(),
|
||||
board_types::dimension::DimensionStyle::Leader(_) => "LEADER".to_string(),
|
||||
board_types::dimension::DimensionStyle::Center(_) => "CENTER".to_string(),
|
||||
});
|
||||
let style = map_dimension_style(dimension.dimension_style);
|
||||
let override_text =
|
||||
(!dimension.override_text.is_empty()).then_some(dimension.override_text);
|
||||
let prefix = (!dimension.prefix.is_empty()).then_some(dimension.prefix);
|
||||
let suffix = (!dimension.suffix.is_empty()).then_some(dimension.suffix);
|
||||
let unit = board_types::DimensionUnit::try_from(dimension.unit)
|
||||
.map(|value| value.as_str_name().to_string())
|
||||
.unwrap_or_else(|_| format!("UNKNOWN({})", dimension.unit));
|
||||
let unit_format = board_types::DimensionUnitFormat::try_from(dimension.unit_format)
|
||||
.map(|value| value.as_str_name().to_string())
|
||||
.unwrap_or_else(|_| format!("UNKNOWN({})", dimension.unit_format));
|
||||
let arrow_direction =
|
||||
board_types::DimensionArrowDirection::try_from(dimension.arrow_direction)
|
||||
.map(|value| value.as_str_name().to_string())
|
||||
.unwrap_or_else(|_| format!("UNKNOWN({})", dimension.arrow_direction));
|
||||
let precision = board_types::DimensionPrecision::try_from(dimension.precision)
|
||||
.map(|value| value.as_str_name().to_string())
|
||||
.unwrap_or_else(|_| format!("UNKNOWN({})", dimension.precision));
|
||||
let text_position = board_types::DimensionTextPosition::try_from(dimension.text_position)
|
||||
.map(|value| value.as_str_name().to_string())
|
||||
.unwrap_or_else(|_| format!("UNKNOWN({})", dimension.text_position));
|
||||
|
||||
return Ok(PcbItem::Dimension(PcbDimension {
|
||||
id: dimension.id.map(|id| id.value),
|
||||
layer: layer_to_model(dimension.layer),
|
||||
locked: map_lock_state(dimension.locked),
|
||||
text: dimension.text.map(|value| value.text),
|
||||
style_kind: dimension.dimension_style.map(|value| format!("{value:?}")),
|
||||
style_kind,
|
||||
style,
|
||||
override_text_enabled: dimension.override_text_enabled,
|
||||
override_text,
|
||||
prefix,
|
||||
suffix,
|
||||
unit: Some(unit),
|
||||
unit_format: Some(unit_format),
|
||||
arrow_direction: Some(arrow_direction),
|
||||
precision: Some(precision),
|
||||
suppress_trailing_zeroes: dimension.suppress_trailing_zeroes,
|
||||
line_thickness_nm: map_optional_distance_nm(dimension.line_thickness),
|
||||
arrow_length_nm: map_optional_distance_nm(dimension.arrow_length),
|
||||
extension_offset_nm: map_optional_distance_nm(dimension.extension_offset),
|
||||
text_position: Some(text_position),
|
||||
keep_text_aligned: dimension.keep_text_aligned,
|
||||
}));
|
||||
}
|
||||
|
||||
|
|
@ -3265,6 +3690,7 @@ fn decode_pcb_item(item: prost_types::Any) -> Result<PcbItem, KiCadError> {
|
|||
id: group.id.map(|id| id.value),
|
||||
name: group.name,
|
||||
item_count: group.items.len(),
|
||||
item_ids: group.items.into_iter().map(|item| item.value).collect(),
|
||||
}));
|
||||
}
|
||||
|
||||
|
|
@ -3458,7 +3884,7 @@ fn format_via_selection_detail(via: board_types::Via) -> String {
|
|||
let via_type = board_types::ViaType::try_from(via.r#type)
|
||||
.map(|value| value.as_str_name().to_string())
|
||||
.unwrap_or_else(|_| format!("UNKNOWN({})", via.r#type));
|
||||
let layers = map_via_layers(via.pad_stack);
|
||||
let layers = map_via_layers(via.pad_stack.as_ref());
|
||||
let pad_layers = layers
|
||||
.as_ref()
|
||||
.map(|row| format_layer_names(&row.padstack_layers))
|
||||
|
|
@ -4297,7 +4723,7 @@ mod tests {
|
|||
},
|
||||
];
|
||||
|
||||
let summary = summarize_selection(items);
|
||||
let summary = summarize_selection(&items);
|
||||
assert_eq!(summary.total_items, 3);
|
||||
assert_eq!(summary.type_url_counts.len(), 2);
|
||||
assert_eq!(summary.type_url_counts[0].count, 2);
|
||||
|
|
@ -4340,6 +4766,35 @@ mod tests {
|
|||
assert!(detail.contains("net=12:GND"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_pcb_item_maps_track_locked_state() {
|
||||
let track = crate::proto::kiapi::board::types::Track {
|
||||
id: Some(crate::proto::kiapi::common::types::Kiid {
|
||||
value: "track-id".to_string(),
|
||||
}),
|
||||
start: None,
|
||||
end: None,
|
||||
width: None,
|
||||
locked: crate::proto::kiapi::common::types::LockedState::LsLocked as i32,
|
||||
layer: crate::proto::kiapi::board::types::BoardLayer::BlFCu as i32,
|
||||
net: None,
|
||||
};
|
||||
|
||||
let item = prost_types::Any {
|
||||
type_url: super::envelope::type_url("kiapi.board.types.Track"),
|
||||
value: track.encode_to_vec(),
|
||||
};
|
||||
|
||||
let parsed = decode_pcb_item(item).expect("track payload should decode");
|
||||
match parsed {
|
||||
PcbItem::Track(track) => {
|
||||
assert_eq!(track.id.as_deref(), Some("track-id"));
|
||||
assert_eq!(track.locked, crate::model::board::ItemLockState::Locked);
|
||||
}
|
||||
other => panic!("expected track item, got {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_pcb_item_maps_via_layers() {
|
||||
let via = crate::proto::kiapi::board::types::Via {
|
||||
|
|
@ -4441,6 +4896,111 @@ mod tests {
|
|||
assert!(detail.contains("drill_span=BL_F_Cu->BL_B_Cu"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_pcb_item_maps_group_item_ids() {
|
||||
let group = crate::proto::kiapi::board::types::Group {
|
||||
id: Some(crate::proto::kiapi::common::types::Kiid {
|
||||
value: "group-id".to_string(),
|
||||
}),
|
||||
name: "group-a".to_string(),
|
||||
items: vec![
|
||||
crate::proto::kiapi::common::types::Kiid {
|
||||
value: "item-1".to_string(),
|
||||
},
|
||||
crate::proto::kiapi::common::types::Kiid {
|
||||
value: "item-2".to_string(),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
let item = prost_types::Any {
|
||||
type_url: super::envelope::type_url("kiapi.board.types.Group"),
|
||||
value: group.encode_to_vec(),
|
||||
};
|
||||
|
||||
let parsed = decode_pcb_item(item).expect("group payload should decode");
|
||||
match parsed {
|
||||
PcbItem::Group(group) => {
|
||||
assert_eq!(group.id.as_deref(), Some("group-id"));
|
||||
assert_eq!(group.item_count, 2);
|
||||
assert_eq!(
|
||||
group.item_ids,
|
||||
vec!["item-1".to_string(), "item-2".to_string()]
|
||||
);
|
||||
}
|
||||
other => panic!("expected group item, got {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_pcb_item_maps_board_text_attributes() {
|
||||
let text = crate::proto::kiapi::board::types::BoardText {
|
||||
id: Some(crate::proto::kiapi::common::types::Kiid {
|
||||
value: "text-id".to_string(),
|
||||
}),
|
||||
text: Some(crate::proto::kiapi::common::types::Text {
|
||||
position: Some(crate::proto::kiapi::common::types::Vector2 {
|
||||
x_nm: 123,
|
||||
y_nm: 456,
|
||||
}),
|
||||
attributes: Some(crate::proto::kiapi::common::types::TextAttributes {
|
||||
font_name: "KiCad Font".to_string(),
|
||||
horizontal_alignment:
|
||||
crate::proto::kiapi::common::types::HorizontalAlignment::HaCenter as i32,
|
||||
vertical_alignment: crate::proto::kiapi::common::types::VerticalAlignment::VaTop
|
||||
as i32,
|
||||
stroke_width: Some(crate::proto::kiapi::common::types::Distance {
|
||||
value_nm: 42,
|
||||
}),
|
||||
italic: true,
|
||||
bold: false,
|
||||
underlined: true,
|
||||
mirrored: false,
|
||||
multiline: true,
|
||||
keep_upright: true,
|
||||
size: Some(crate::proto::kiapi::common::types::Vector2 {
|
||||
x_nm: 777,
|
||||
y_nm: 888,
|
||||
}),
|
||||
..Default::default()
|
||||
}),
|
||||
text: "HELLO".to_string(),
|
||||
hyperlink: "https://example.com".to_string(),
|
||||
}),
|
||||
layer: crate::proto::kiapi::board::types::BoardLayer::BlFSilkS as i32,
|
||||
knockout: true,
|
||||
locked: crate::proto::kiapi::common::types::LockedState::LsUnlocked as i32,
|
||||
};
|
||||
|
||||
let item = prost_types::Any {
|
||||
type_url: super::envelope::type_url("kiapi.board.types.BoardText"),
|
||||
value: text.encode_to_vec(),
|
||||
};
|
||||
|
||||
let parsed = decode_pcb_item(item).expect("board text payload should decode");
|
||||
match parsed {
|
||||
PcbItem::BoardText(text) => {
|
||||
assert_eq!(text.id.as_deref(), Some("text-id"));
|
||||
assert_eq!(text.text.as_deref(), Some("HELLO"));
|
||||
assert_eq!(text.hyperlink.as_deref(), Some("https://example.com"));
|
||||
assert_eq!(text.knockout, true);
|
||||
let attributes = text.attributes.expect("text attributes should map");
|
||||
assert_eq!(attributes.font_name.as_deref(), Some("KiCad Font"));
|
||||
assert_eq!(
|
||||
attributes.horizontal_alignment.as_deref(),
|
||||
Some("HA_CENTER")
|
||||
);
|
||||
assert_eq!(attributes.vertical_alignment.as_deref(), Some("VA_TOP"));
|
||||
assert_eq!(attributes.stroke_width_nm, Some(42));
|
||||
assert_eq!(
|
||||
attributes.size_nm.map(|v| (v.x_nm, v.y_nm)),
|
||||
Some((777, 888))
|
||||
);
|
||||
}
|
||||
other => panic!("expected board text item, got {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pad_netlist_from_footprint_items_extracts_pad_entries() {
|
||||
let pad = crate::proto::kiapi::board::types::Pad {
|
||||
|
|
|
|||
22
src/lib.rs
22
src/lib.rs
|
|
@ -90,18 +90,20 @@ pub use crate::model::board::{
|
|||
ArcStartMidEndNm, BoardEditorAppearanceSettings, BoardEnabledLayers, BoardFlipMode,
|
||||
BoardLayerClass, BoardLayerGraphicsDefault, BoardLayerInfo, BoardNet, BoardOriginKind,
|
||||
BoardStackup, BoardStackupDielectricProperties, BoardStackupLayer, BoardStackupLayerType,
|
||||
ColorRgba, DrcSeverity, GraphicsDefaults, InactiveLayerDisplayMode, NetClassBoardSettings,
|
||||
NetClassForNetEntry, NetClassInfo, NetClassType, NetColorDisplayMode, PadNetEntry,
|
||||
PadShapeAsPolygonEntry, PadstackPresenceEntry, PadstackPresenceState, PcbArc,
|
||||
PcbBoardGraphicShape, PcbBoardText, PcbBoardTextBox, PcbDimension, PcbField, PcbFootprint,
|
||||
PcbGroup, PcbItem, PcbPad, PcbPadType, PcbTrack, PcbUnknownItem, PcbVia, PcbViaLayers,
|
||||
PcbViaType, PcbZone, PcbZoneType, PolyLineNm, PolyLineNodeGeometryNm, PolygonWithHolesNm,
|
||||
RatsnestDisplayMode, Vector2Nm,
|
||||
ColorRgba, DrcSeverity, GraphicsDefaults, InactiveLayerDisplayMode, ItemLockState,
|
||||
NetClassBoardSettings, NetClassForNetEntry, NetClassInfo, NetClassType, NetColorDisplayMode,
|
||||
PadNetEntry, PadShapeAsPolygonEntry, PadstackPresenceEntry, PadstackPresenceState, PcbArc,
|
||||
PcbBoardGraphicShape, PcbBoardText, PcbBoardTextBox, PcbDimension, PcbDimensionStyle, PcbField,
|
||||
PcbFootprint, PcbFootprintSymbolLink, PcbGraphicShapeGeometry, PcbGroup, PcbItem, PcbPad,
|
||||
PcbPadStack, PcbPadType, PcbPadstackDrill, PcbSymbolPinInfo, PcbTextAttributes, PcbTrack,
|
||||
PcbUnknownItem, PcbVia, PcbViaLayers, PcbViaType, PcbZone, PcbZoneLayerProperty, PcbZoneType,
|
||||
PolyLineNm, PolyLineNodeGeometryNm, PolygonWithHolesNm, RatsnestDisplayMode, Vector2Nm,
|
||||
};
|
||||
pub use crate::model::common::{
|
||||
CommitAction, CommitSession, DocumentSpecifier, DocumentType, EditorFrameType, ItemBoundingBox,
|
||||
ItemHitTestResult, MapMergeMode, PcbObjectTypeCode, RunActionStatus, SelectionItemDetail,
|
||||
SelectionSummary, SelectionTypeCount, TextAsShapesEntry, TextAttributesSpec, TextBoxSpec,
|
||||
TextExtents, TextHorizontalAlignment, TextObjectSpec, TextShape, TextShapeGeometry, TextSpec,
|
||||
TextVerticalAlignment, TitleBlockInfo, VersionInfo,
|
||||
SelectionMutationResult, SelectionStringDump, SelectionSummary, SelectionTypeCount,
|
||||
TextAsShapesEntry, TextAttributesSpec, TextBoxSpec, TextExtents, TextHorizontalAlignment,
|
||||
TextObjectSpec, TextShape, TextShapeGeometry, TextSpec, TextVerticalAlignment, TitleBlockInfo,
|
||||
VersionInfo,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -386,12 +386,144 @@ pub enum PcbZoneType {
|
|||
Unknown(i32),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum ItemLockState {
|
||||
Unlocked,
|
||||
Locked,
|
||||
Unknown(i32),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct PcbPadstackDrill {
|
||||
pub start_layer: BoardLayerInfo,
|
||||
pub end_layer: BoardLayerInfo,
|
||||
pub diameter_nm: Option<Vector2Nm>,
|
||||
pub shape: Option<String>,
|
||||
pub capped: Option<String>,
|
||||
pub filled: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct PcbPadStack {
|
||||
pub stack_type: Option<String>,
|
||||
pub layers: Vec<BoardLayerInfo>,
|
||||
pub drill: Option<PcbPadstackDrill>,
|
||||
pub unconnected_layer_removal: Option<String>,
|
||||
pub copper_layer_count: usize,
|
||||
pub has_front_outer_layers: bool,
|
||||
pub has_back_outer_layers: bool,
|
||||
pub has_zone_settings: bool,
|
||||
pub secondary_drill: Option<PcbPadstackDrill>,
|
||||
pub tertiary_drill: Option<PcbPadstackDrill>,
|
||||
pub has_front_post_machining: bool,
|
||||
pub has_back_post_machining: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct PcbSymbolPinInfo {
|
||||
pub name: String,
|
||||
pub pin_type: Option<String>,
|
||||
pub no_connect: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct PcbTextAttributes {
|
||||
pub font_name: Option<String>,
|
||||
pub horizontal_alignment: Option<String>,
|
||||
pub vertical_alignment: Option<String>,
|
||||
pub stroke_width_nm: Option<i64>,
|
||||
pub italic: bool,
|
||||
pub bold: bool,
|
||||
pub underlined: bool,
|
||||
pub mirrored: bool,
|
||||
pub multiline: bool,
|
||||
pub keep_upright: bool,
|
||||
pub size_nm: Option<Vector2Nm>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum PcbGraphicShapeGeometry {
|
||||
Segment {
|
||||
start_nm: Option<Vector2Nm>,
|
||||
end_nm: Option<Vector2Nm>,
|
||||
},
|
||||
Rectangle {
|
||||
top_left_nm: Option<Vector2Nm>,
|
||||
bottom_right_nm: Option<Vector2Nm>,
|
||||
corner_radius_nm: Option<i64>,
|
||||
},
|
||||
Arc {
|
||||
start_nm: Option<Vector2Nm>,
|
||||
mid_nm: Option<Vector2Nm>,
|
||||
end_nm: Option<Vector2Nm>,
|
||||
},
|
||||
Circle {
|
||||
center_nm: Option<Vector2Nm>,
|
||||
radius_point_nm: Option<Vector2Nm>,
|
||||
},
|
||||
Polygon {
|
||||
polygon_count: usize,
|
||||
},
|
||||
Bezier {
|
||||
start_nm: Option<Vector2Nm>,
|
||||
control1_nm: Option<Vector2Nm>,
|
||||
control2_nm: Option<Vector2Nm>,
|
||||
end_nm: Option<Vector2Nm>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct PcbZoneLayerProperty {
|
||||
pub layer: BoardLayerInfo,
|
||||
pub hatching_offset_nm: Option<Vector2Nm>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum PcbDimensionStyle {
|
||||
Aligned {
|
||||
start_nm: Option<Vector2Nm>,
|
||||
end_nm: Option<Vector2Nm>,
|
||||
height_nm: Option<i64>,
|
||||
extension_height_nm: Option<i64>,
|
||||
},
|
||||
Orthogonal {
|
||||
start_nm: Option<Vector2Nm>,
|
||||
end_nm: Option<Vector2Nm>,
|
||||
height_nm: Option<i64>,
|
||||
extension_height_nm: Option<i64>,
|
||||
alignment: Option<String>,
|
||||
},
|
||||
Radial {
|
||||
center_nm: Option<Vector2Nm>,
|
||||
radius_point_nm: Option<Vector2Nm>,
|
||||
leader_length_nm: Option<i64>,
|
||||
},
|
||||
Leader {
|
||||
start_nm: Option<Vector2Nm>,
|
||||
end_nm: Option<Vector2Nm>,
|
||||
border_style: Option<String>,
|
||||
},
|
||||
Center {
|
||||
center_nm: Option<Vector2Nm>,
|
||||
end_nm: Option<Vector2Nm>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct PcbFootprintSymbolLink {
|
||||
pub has_symbol_path: bool,
|
||||
pub sheet_name: Option<String>,
|
||||
pub sheet_filename: Option<String>,
|
||||
pub footprint_filters: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct PcbTrack {
|
||||
pub id: Option<String>,
|
||||
pub start_nm: Option<Vector2Nm>,
|
||||
pub end_nm: Option<Vector2Nm>,
|
||||
pub width_nm: Option<i64>,
|
||||
pub locked: ItemLockState,
|
||||
pub layer: BoardLayerInfo,
|
||||
pub net: Option<BoardNet>,
|
||||
}
|
||||
|
|
@ -403,6 +535,7 @@ pub struct PcbArc {
|
|||
pub mid_nm: Option<Vector2Nm>,
|
||||
pub end_nm: Option<Vector2Nm>,
|
||||
pub width_nm: Option<i64>,
|
||||
pub locked: ItemLockState,
|
||||
pub layer: BoardLayerInfo,
|
||||
pub net: Option<BoardNet>,
|
||||
}
|
||||
|
|
@ -412,7 +545,9 @@ pub struct PcbVia {
|
|||
pub id: Option<String>,
|
||||
pub position_nm: Option<Vector2Nm>,
|
||||
pub via_type: PcbViaType,
|
||||
pub locked: ItemLockState,
|
||||
pub layers: Option<PcbViaLayers>,
|
||||
pub pad_stack: Option<PcbPadStack>,
|
||||
pub net: Option<BoardNet>,
|
||||
}
|
||||
|
||||
|
|
@ -423,15 +558,30 @@ pub struct PcbFootprint {
|
|||
pub position_nm: Option<Vector2Nm>,
|
||||
pub orientation_deg: Option<f64>,
|
||||
pub layer: BoardLayerInfo,
|
||||
pub locked: ItemLockState,
|
||||
pub value: Option<String>,
|
||||
pub datasheet: Option<String>,
|
||||
pub description: Option<String>,
|
||||
pub has_attributes: bool,
|
||||
pub has_overrides: bool,
|
||||
pub has_definition: bool,
|
||||
pub definition_item_count: usize,
|
||||
pub symbol_link: Option<PcbFootprintSymbolLink>,
|
||||
pub pad_count: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct PcbPad {
|
||||
pub id: Option<String>,
|
||||
pub locked: ItemLockState,
|
||||
pub number: String,
|
||||
pub pad_type: PcbPadType,
|
||||
pub position_nm: Option<Vector2Nm>,
|
||||
pub pad_stack: Option<PcbPadStack>,
|
||||
pub copper_clearance_override_nm: Option<i64>,
|
||||
pub pad_to_die_length_nm: Option<i64>,
|
||||
pub pad_to_die_delay_as: Option<i64>,
|
||||
pub symbol_pin: Option<PcbSymbolPinInfo>,
|
||||
pub net: Option<BoardNet>,
|
||||
}
|
||||
|
||||
|
|
@ -439,8 +589,13 @@ pub struct PcbPad {
|
|||
pub struct PcbBoardGraphicShape {
|
||||
pub id: Option<String>,
|
||||
pub layer: BoardLayerInfo,
|
||||
pub locked: ItemLockState,
|
||||
pub net: Option<BoardNet>,
|
||||
pub geometry_kind: Option<String>,
|
||||
pub geometry: Option<PcbGraphicShapeGeometry>,
|
||||
pub stroke_width_nm: Option<i64>,
|
||||
pub stroke_style: Option<String>,
|
||||
pub fill_type: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
|
|
@ -448,6 +603,11 @@ pub struct PcbBoardText {
|
|||
pub id: Option<String>,
|
||||
pub layer: BoardLayerInfo,
|
||||
pub text: Option<String>,
|
||||
pub position_nm: Option<Vector2Nm>,
|
||||
pub hyperlink: Option<String>,
|
||||
pub attributes: Option<PcbTextAttributes>,
|
||||
pub knockout: bool,
|
||||
pub locked: ItemLockState,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
|
|
@ -455,6 +615,10 @@ pub struct PcbBoardTextBox {
|
|||
pub id: Option<String>,
|
||||
pub layer: BoardLayerInfo,
|
||||
pub text: Option<String>,
|
||||
pub top_left_nm: Option<Vector2Nm>,
|
||||
pub bottom_right_nm: Option<Vector2Nm>,
|
||||
pub attributes: Option<PcbTextAttributes>,
|
||||
pub locked: ItemLockState,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
|
|
@ -469,17 +633,42 @@ pub struct PcbZone {
|
|||
pub id: Option<String>,
|
||||
pub name: String,
|
||||
pub zone_type: PcbZoneType,
|
||||
pub layers: Vec<BoardLayerInfo>,
|
||||
pub layer_count: usize,
|
||||
pub priority: u32,
|
||||
pub locked: ItemLockState,
|
||||
pub filled: bool,
|
||||
pub polygon_count: usize,
|
||||
pub outline_polygon_count: usize,
|
||||
pub has_copper_settings: bool,
|
||||
pub has_rule_area_settings: bool,
|
||||
pub border_style: Option<String>,
|
||||
pub border_pitch_nm: Option<i64>,
|
||||
pub layer_properties: Vec<PcbZoneLayerProperty>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct PcbDimension {
|
||||
pub id: Option<String>,
|
||||
pub layer: BoardLayerInfo,
|
||||
pub locked: ItemLockState,
|
||||
pub text: Option<String>,
|
||||
pub style_kind: Option<String>,
|
||||
pub style: Option<PcbDimensionStyle>,
|
||||
pub override_text_enabled: bool,
|
||||
pub override_text: Option<String>,
|
||||
pub prefix: Option<String>,
|
||||
pub suffix: Option<String>,
|
||||
pub unit: Option<String>,
|
||||
pub unit_format: Option<String>,
|
||||
pub arrow_direction: Option<String>,
|
||||
pub precision: Option<String>,
|
||||
pub suppress_trailing_zeroes: bool,
|
||||
pub line_thickness_nm: Option<i64>,
|
||||
pub arrow_length_nm: Option<i64>,
|
||||
pub extension_offset_nm: Option<i64>,
|
||||
pub text_position: Option<String>,
|
||||
pub keep_text_aligned: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
|
|
@ -487,6 +676,7 @@ pub struct PcbGroup {
|
|||
pub id: Option<String>,
|
||||
pub name: String,
|
||||
pub item_count: usize,
|
||||
pub item_ids: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::model::board::{ColorRgba, PolygonWithHolesNm, Vector2Nm};
|
||||
use crate::model::board::{ColorRgba, PcbItem, PolygonWithHolesNm, Vector2Nm};
|
||||
use crate::proto::kiapi::common::types as common_types;
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
|
|
@ -209,6 +209,24 @@ pub struct SelectionItemDetail {
|
|||
pub raw_len: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
/// Selection dump returned by `get_selection_as_string`.
|
||||
pub struct SelectionStringDump {
|
||||
/// Ordered ids included in the serialized selection payload.
|
||||
pub ids: Vec<String>,
|
||||
/// Selection serialized as KiCad s-expression text.
|
||||
pub contents: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
/// Result of add/remove/clear selection mutations.
|
||||
pub struct SelectionMutationResult {
|
||||
/// Decoded selected items after mutation.
|
||||
pub items: Vec<PcbItem>,
|
||||
/// Compact composition summary for the same selection state.
|
||||
pub summary: SelectionSummary,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
/// Opaque commit session identifier returned by `begin_commit`.
|
||||
pub struct CommitSession {
|
||||
|
|
|
|||
|
|
@ -594,32 +594,32 @@ fn run() -> Result<(), KiCadError> {
|
|||
}
|
||||
}
|
||||
Command::AddToSelection { item_ids } => {
|
||||
let summary = client.add_to_selection(item_ids)?;
|
||||
println!("selection_total={}", summary.total_items);
|
||||
for entry in summary.type_url_counts {
|
||||
let result = client.add_to_selection(item_ids)?;
|
||||
println!("selection_total={}", result.summary.total_items);
|
||||
for entry in result.summary.type_url_counts {
|
||||
println!("type_url={} count={}", entry.type_url, entry.count);
|
||||
}
|
||||
}
|
||||
Command::RemoveFromSelection { item_ids } => {
|
||||
let summary = client.remove_from_selection(item_ids)?;
|
||||
println!("selection_total={}", summary.total_items);
|
||||
for entry in summary.type_url_counts {
|
||||
let result = client.remove_from_selection(item_ids)?;
|
||||
println!("selection_total={}", result.summary.total_items);
|
||||
for entry in result.summary.type_url_counts {
|
||||
println!("type_url={} count={}", entry.type_url, entry.count);
|
||||
}
|
||||
}
|
||||
Command::ClearSelection => {
|
||||
let summary = client.clear_selection()?;
|
||||
println!("selection_total={}", summary.total_items);
|
||||
let result = client.clear_selection()?;
|
||||
println!("selection_total={}", result.summary.total_items);
|
||||
}
|
||||
Command::SelectionSummary => {
|
||||
let summary = client.get_selection_summary()?;
|
||||
let summary = client.get_selection_summary(Vec::new())?;
|
||||
println!("selection_total={}", summary.total_items);
|
||||
for entry in summary.type_url_counts {
|
||||
println!("type_url={} count={}", entry.type_url, entry.count);
|
||||
}
|
||||
}
|
||||
Command::SelectionDetails => {
|
||||
let details = client.get_selection_details()?;
|
||||
let details = client.get_selection_details(Vec::new())?;
|
||||
println!("selection_total={}", details.len());
|
||||
for (index, item) in details.iter().enumerate() {
|
||||
println!(
|
||||
|
|
@ -629,7 +629,7 @@ fn run() -> Result<(), KiCadError> {
|
|||
}
|
||||
}
|
||||
Command::SelectionRaw => {
|
||||
let items = client.get_selection_raw()?;
|
||||
let items = client.get_selection_raw(Vec::new())?;
|
||||
println!("selection_total={}", items.len());
|
||||
for (index, item) in items.iter().enumerate() {
|
||||
println!(
|
||||
|
|
@ -851,8 +851,12 @@ fn run() -> Result<(), KiCadError> {
|
|||
println!("{content}");
|
||||
}
|
||||
Command::SelectionAsString => {
|
||||
let content = client.get_selection_as_string()?;
|
||||
println!("{content}");
|
||||
let selection = client.get_selection_as_string()?;
|
||||
println!("selection_id_count={}", selection.ids.len());
|
||||
for id in selection.ids {
|
||||
println!("id={id}");
|
||||
}
|
||||
println!("{}", selection.contents);
|
||||
}
|
||||
Command::Stackup => {
|
||||
let stackup = client.get_board_stackup()?;
|
||||
|
|
@ -2140,7 +2144,7 @@ COMMANDS:
|
|||
Check padstack shape presence matrix across layers
|
||||
title-block Show title block fields
|
||||
board-as-string Dump board as KiCad s-expression text
|
||||
selection-as-string Dump current selection as KiCad s-expression text
|
||||
selection-as-string Dump current selection IDs + KiCad s-expression text
|
||||
stackup Show typed board stackup
|
||||
update-stackup Round-trip current stackup through UpdateBoardStackup
|
||||
graphics-defaults Show typed graphics defaults
|
||||
|
|
|
|||
Loading…
Reference in New Issue