kicad/
board.rs

1// This program source code file is part of KiCad, a free EDA CAD application.
2//
3// Copyright (C) 2024 KiCad Developers
4//
5// This program is free software: you can redistribute it and/or modify it
6// under the terms of the GNU General Public License as published by the
7// Free Software Foundation, either version 3 of the License, or (at your
8// option) any later version.
9//
10// This program is distributed in the hope that it will be useful, but
11// WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13// General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License along
16// with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18use enum_as_inner::EnumAsInner;
19use protobuf::{well_known_types::any::Any, EnumOrUnknown};
20
21use crate::protos::{base_types::*, board_types, editor_commands::*, enums::*};
22use crate::{Coord, KiCad, KiCadError, KiCadResult};
23
24#[derive(Debug)]
25pub struct Board<'a> {
26    pub(crate) kicad: &'a KiCad,
27    pub(crate) doc: DocumentSpecifier,
28}
29
30impl Board<'_> {
31    fn create_item_header(&self) -> ItemHeader {
32        let mut header = ItemHeader::new();
33        header.document = Some(self.doc.clone()).into();
34        header
35    }
36
37    fn unpack_any(any: &Any) -> KiCadResult<BoardItem> {
38        match any.type_url.as_str() {
39            "type.googleapis.com/kiapi.board.types.Track" => Ok(BoardItem::Track(Track(
40                Any::unpack::<board_types::Track>(any)?.unwrap(),
41            ))),
42            "type.googleapis.com/kiapi.board.types.Arc" => Ok(BoardItem::ArcTrack(ArcTrack(
43                Any::unpack::<board_types::Arc>(any)?.unwrap(),
44            ))),
45            "type.googleapis.com/kiapi.board.types.Via" => {
46                Ok(BoardItem::Via(Via(
47                    Any::unpack::<board_types::Via>(any)?.unwrap()
48                )))
49            }
50            _ => Err(KiCadError::ApiError(format!(
51                "unknown type: {}",
52                any.type_url.as_str()
53            ))),
54        }
55    }
56
57    pub fn name(&self) -> &str {
58        self.doc.board_filename()
59    }
60
61    pub fn create_items(
62        &self,
63        items: Vec<impl GenericBoardItem>,
64    ) -> KiCadResult<Vec<ItemCreationResult>> {
65        let mut message = CreateItems::new();
66        message.header = Some(self.create_item_header()).into();
67        message.items.extend(items.iter().map(|t| t.pack()));
68
69        let reply = self.kicad.send_request::<_, CreateItemsResponse>(message)?;
70        self.check_item_request_status(&reply.status)?;
71
72        Ok(reply.created_items)
73    }
74
75    pub fn get_items(&self, types: &[KiCadObjectType]) -> KiCadResult<Vec<BoardItem>> {
76        let mut message = GetItems::new();
77        message.header = Some(self.create_item_header()).into();
78        message
79            .types
80            .extend(types.iter().map(|t| EnumOrUnknown::new(*t)));
81
82        let reply = self.kicad.send_request::<_, GetItemsResponse>(message)?;
83        self.check_item_request_status(&reply.status)?;
84
85        let mut items: Vec<BoardItem> = vec![];
86
87        for item in reply.items {
88            items.push(Self::unpack_any(&item)?)
89        }
90
91        Ok(items)
92    }
93
94    // TODO: if we want this to match the old bindings, this should return
95    // an enum of { Track, ArcTrack, Via } - should create a trait for the
96    // shared properties between them?
97    pub fn get_tracks(&self) -> KiCadResult<Vec<Track>> {
98        // TODO: lots of copying here?
99        Ok(self
100            .get_items(&[KiCadObjectType::KOT_PCB_TRACE])?
101            .into_iter()
102            .filter(|i| matches!(i, BoardItem::Track(_)))
103            .map(|i| i.into_track().unwrap())
104            .collect())
105    }
106
107    pub fn update_items(
108        &self,
109        items: &Vec<impl GenericBoardItem>,
110    ) -> KiCadResult<Vec<ItemUpdateResult>> {
111        let mut message = UpdateItems::new();
112        message.header = Some(self.create_item_header()).into();
113        message.items.extend(items.iter().map(|t| t.pack()));
114
115        let reply = self.kicad.send_request::<_, UpdateItemsResponse>(message)?;
116        self.check_item_request_status(&reply.status)?;
117        Ok(reply.updated_items)
118    }
119
120    pub fn delete_items(
121        &self,
122        items: &Vec<impl GenericBoardItem>,
123    ) -> KiCadResult<Vec<ItemDeletionResult>> {
124        let mut message = DeleteItems::new();
125        message.header = Some(self.create_item_header()).into();
126        message
127            .item_ids
128            .extend(items.iter().map(|i| i.id().clone()));
129
130        let reply = self.kicad.send_request::<_, DeleteItemsResponse>(message)?;
131        self.check_item_request_status(&reply.status)?;
132        Ok(reply.deleted_items)
133    }
134
135    fn check_item_request_status(&self, r: &EnumOrUnknown<ItemRequestStatus>) -> KiCadResult<()> {
136        match r.enum_value_or_default() {
137            ItemRequestStatus::IRS_OK => Ok(()),
138            ItemRequestStatus::IRS_DOCUMENT_NOT_FOUND => Err(KiCadError::ApiError(format!(
139                "KiCad reported that the file {} is not open",
140                self.name()
141            ))),
142            _ => Err(
143                // TODO: real error
144                KiCadError::ApiError(String::from("unexpected item request status")),
145            ),
146        }
147    }
148}
149
150#[derive(Debug, EnumAsInner)]
151pub enum BoardItem {
152    Track(Track),
153    ArcTrack(ArcTrack),
154    Via(Via),
155}
156
157pub trait GenericBoardItem {
158    fn id(&self) -> &KIID;
159
160    fn pack(&self) -> Any;
161}
162
163#[derive(Debug, Clone, PartialEq)]
164pub struct Track(board_types::Track);
165
166impl Track {
167    pub fn new() -> Self {
168        Track(board_types::Track::new())
169    }
170
171    pub fn start(&self) -> (Coord, Coord) {
172        (self.0.start.x_nm, self.0.start.y_nm)
173    }
174
175    pub fn set_start(&mut self, (x, y): (Coord, Coord)) {
176        let start = self.0.start.mut_or_insert_default();
177        start.x_nm = x;
178        start.y_nm = y;
179    }
180
181    pub fn end(&self) -> (Coord, Coord) {
182        (self.0.end.x_nm, self.0.end.y_nm)
183    }
184
185    pub fn set_end(&mut self, (x, y): (Coord, Coord)) {
186        let end = self.0.end.mut_or_insert_default();
187        end.x_nm = x;
188        end.y_nm = y;
189    }
190
191    pub fn width(&self) -> Coord {
192        self.0.width.get_or_default().value_nm
193    }
194
195    pub fn set_width(&mut self, width: Coord) {
196        self.0.width.mut_or_insert_default().value_nm = width
197    }
198
199    pub fn locked(&self) -> bool {
200        self.0.locked.enum_value_or_default() == LockedState::LS_LOCKED
201    }
202
203    pub fn layer(&self) -> board_types::BoardLayer {
204        self.0.layer.unwrap()
205    }
206
207    pub fn net(&self) -> &board_types::Net {
208        self.0.net.get_or_default()
209    }
210}
211
212impl GenericBoardItem for Track {
213    fn id(&self) -> &KIID {
214        self.0.id.get_or_default()
215    }
216
217    fn pack(&self) -> Any {
218        Any::pack(&self.0).unwrap_or_default()
219    }
220}
221
222#[derive(Debug, Clone, PartialEq)]
223pub struct ArcTrack(board_types::Arc);
224
225impl ArcTrack {
226    pub fn new() -> Self {
227        ArcTrack(board_types::Arc::new())
228    }
229
230    pub fn start(&self) -> (Coord, Coord) {
231        (self.0.start.x_nm, self.0.start.y_nm)
232    }
233
234    pub fn set_start(&mut self, (x, y): (Coord, Coord)) {
235        let start = self.0.start.mut_or_insert_default();
236        start.x_nm = x;
237        start.y_nm = y;
238    }
239
240    pub fn mid(&self) -> (Coord, Coord) {
241        (self.0.mid.x_nm, self.0.mid.y_nm)
242    }
243
244    pub fn set_mid(&mut self, (x, y): (Coord, Coord)) {
245        let mid = self.0.mid.mut_or_insert_default();
246        mid.x_nm = x;
247        mid.y_nm = y;
248    }
249
250    pub fn end(&self) -> (Coord, Coord) {
251        (self.0.end.x_nm, self.0.end.y_nm)
252    }
253
254    pub fn set_end(&mut self, (x, y): (Coord, Coord)) {
255        let end = self.0.end.mut_or_insert_default();
256        end.x_nm = x;
257        end.y_nm = y;
258    }
259
260    pub fn width(&self) -> Coord {
261        self.0.width.get_or_default().value_nm
262    }
263
264    pub fn set_width(&mut self, width: Coord) {
265        self.0.width.mut_or_insert_default().value_nm = width
266    }
267
268    pub fn locked(&self) -> bool {
269        self.0.locked.enum_value_or_default() == LockedState::LS_LOCKED
270    }
271
272    pub fn layer(&self) -> board_types::BoardLayer {
273        self.0.layer.unwrap()
274    }
275
276    pub fn set_layer(&mut self, layer: board_types::BoardLayer) {
277        self.0.layer = layer.into();
278    }
279
280    pub fn net(&self) -> &board_types::Net {
281        self.0.net.get_or_default()
282    }
283}
284
285impl GenericBoardItem for ArcTrack {
286    fn id(&self) -> &KIID {
287        self.0.id.get_or_default()
288    }
289
290    fn pack(&self) -> Any {
291        Any::pack(&self.0).unwrap_or_default()
292    }
293}
294
295#[derive(Debug, Clone, PartialEq)]
296pub struct Via(board_types::Via);
297
298impl Via {
299    pub fn new() -> Self {
300        Via(board_types::Via::new())
301    }
302
303    pub fn position(&self) -> (Coord, Coord) {
304        (self.0.position.x_nm, self.0.position.y_nm)
305    }
306
307    pub fn set_position(&mut self, (x, y): (Coord, Coord)) {
308        let position = self.0.position.mut_or_insert_default();
309        position.x_nm = x;
310        position.y_nm = y;
311    }
312
313    pub fn locked(&self) -> bool {
314        self.0.locked.enum_value_or_default() == LockedState::LS_LOCKED
315    }
316
317    pub fn pad_stack(&self) -> &board_types::PadStack {
318        self.0.pad_stack.get_or_default()
319    }
320
321    pub fn net(&self) -> &board_types::Net {
322        self.0.net.get_or_default()
323    }
324}
325
326impl GenericBoardItem for Via {
327    fn id(&self) -> &KIID {
328        self.0.id.get_or_default()
329    }
330
331    fn pack(&self) -> Any {
332        Any::pack(&self.0).unwrap_or_default()
333    }
334}