1use 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 pub fn get_tracks(&self) -> KiCadResult<Vec<Track>> {
98 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 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}