1use crate::component_placement::{ComponentPlacement, parse_component_placement_section};
2use crate::drilled_holes::{Hole, parse_drilled_holes_section};
3use crate::headers::{BoardPanelHeader, parse_board_or_panel_header};
4use crate::notes::{Note, parse_notes_section};
5use crate::outlines::{
6 BoardPanelOutline, OtherOutline, PlacementGroupArea, PlacementKeepout, PlacementOutline,
7 RoutingKeepout, RoutingOutline, ViaKeepout, parse_board_panel_outline, parse_other_outline,
8 parse_placement_group_area, parse_placement_keepout, parse_placement_outline,
9 parse_routing_keepout, parse_routing_outline, parse_via_keepout,
10};
11use nom::Parser;
12use nom::multi::{many_m_n, many0};
13
14#[derive(Debug, PartialEq, Clone, Default, PartialOrd)]
16pub struct BoardPanel {
17 pub header: BoardPanelHeader,
18 pub outline: BoardPanelOutline,
19 pub other_outlines: Vec<OtherOutline>,
20 pub routing_outlines: Vec<RoutingOutline>,
21 pub placement_outlines: Vec<PlacementOutline>,
22 pub routing_keepouts: Vec<RoutingKeepout>,
23 pub via_keepouts: Vec<ViaKeepout>,
24 pub placement_keepouts: Vec<PlacementKeepout>,
25 pub placement_group_areas: Vec<PlacementGroupArea>,
26 pub drilled_holes: Vec<Hole>,
27 pub notes: Vec<Note>,
28 pub component_placements: Vec<ComponentPlacement>,
29}
30
31pub fn parse_board_or_panel(input: &str) -> Result<BoardPanel, nom::Err<nom::error::Error<&str>>> {
34 let (
35 remaining,
36 (
37 header,
38 outline,
39 other_outlines,
40 routing_outlines,
41 placement_outlines,
42 routing_keepouts,
43 via_keepouts,
44 placement_keepouts,
45 placement_group_areas,
46 drilled_holes,
47 wrapped_notes,
48 component_placements,
49 ),
50 ) = (
51 parse_board_or_panel_header,
52 parse_board_panel_outline,
53 many0(parse_other_outline),
55 many0(parse_routing_outline),
56 many0(parse_placement_outline),
57 many0(parse_routing_keepout),
58 many0(parse_via_keepout),
59 many0(parse_placement_keepout),
60 many0(parse_placement_group_area),
61 parse_drilled_holes_section,
63 many_m_n(0, 1, parse_notes_section),
65 parse_component_placement_section,
67 )
68 .parse(input)?;
69
70 let notes: Vec<Note> = if wrapped_notes.len() == 1 {
72 wrapped_notes[0].clone()
73 } else if wrapped_notes.is_empty() {
74 Vec::new()
75 } else {
76 panic!(
77 "Expected either 1 or no notes sections, but found: {}",
78 wrapped_notes.len()
79 );
80 };
81
82 let board_panel = BoardPanel {
83 header,
84 outline,
85 other_outlines,
86 routing_outlines,
87 placement_outlines,
88 routing_keepouts,
89 via_keepouts,
90 placement_keepouts,
91 placement_group_areas,
92 drilled_holes,
93 notes,
94 component_placements,
95 };
96
97 if !remaining.is_empty() {
99 Err(nom::Err::Error(nom::error::Error::new(
100 remaining,
101 nom::error::ErrorKind::Eof,
102 )))
103 } else {
104 Ok(board_panel)
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111 use crate::point::Point;
112 #[test]
113 fn test_parse_board() {
114 let input = ".HEADER
115BOARD_FILE 3.0 \"Sample File Generator\" 10/22/96.16:02:44 1
116sample_board THOU
117.END_HEADER
118.BOARD_OUTLINE MCAD
11962.0
1200 5030.5 -120.0 0.0
1211 3000.0 2350.0 360.0
122.END_BOARD_OUTLINE
123.ROUTE_OUTLINE ECAD
124ALL
1250 5112.5 150.0 0.0
1260 5112.5 150.0 0.0
127.END_ROUTE_OUTLINE
128.PLACE_OUTLINE MCAD
129TOP 1000.0
1300 5080.0 2034.9 0.0
1310 5080.0 2034.9 0.0
132.END_PLACE_OUTLINE
133.PLACE_OUTLINE UNOWNED
134BOTTOM 200.0
1350 300.0 200.0 0.0
1360 4800.0 200.0 0.0
137.END_PLACE_OUTLINE
138.ROUTE_KEEPOUT ECAD
139ALL
1400 2650.0 2350.0 0.0
1410 3100.0 2350.0 360.0
142.END_ROUTE_KEEPOUT
143.PLACE_KEEPOUT MCAD
144BOTH 0.0
1450 2650.0 2350.0 0.0
1460 3100.0 2350.0 360.0
147.END_PLACE_KEEPOUT
148.PLACE_KEEPOUT MCAD
149TOP 300.0
1500 3700.0 5000.0 0.0
1510 3700.0 5000.0 0.0
152.END_PLACE_KEEPOUT
153.DRILLED_HOLES
15430.0 1800.0 100.0 PTH J1 PIN ECAD
15520.0 2000.0 1600.0 PTH BOARD VIA ECAD
15693.0 5075.0 0.0 PTH BOARD MTG UNOWNED
15793.0 0.0 4800.0 NPTH BOARD TOOL MCAD
158.END_DRILLED_HOLES
159.NOTES
1601800.0 300.0 75.0 1700.0 \"Do not move connectors!\"
161.END_NOTES
162.PLACEMENT
163cs13_a pn-cap C1
1644000.0 1000.0 100.0 0.0 TOP PLACED
165cc1210 pn-cc1210 C2
1663000.0 3500.0 0.0 0.0 TOP PLACED
167cc1210 pn-cc1210 C3
1683200.0 1800.0 0.0 0.0 BOTTOM PLACED
169.END_PLACEMENT";
170
171 let header = BoardPanelHeader {
173 file_type: "BOARD_FILE".to_string(),
174 version: 3,
175 system_id: "Sample File Generator".to_string(),
176 date: "10/22/96.16:02:44".to_string(),
177 file_version: 1,
178 board_name: "sample_board".to_string(),
179 units: "THOU".to_string(),
180 };
181
182 let outline = BoardPanelOutline {
183 owner: "MCAD".to_string(),
184 thickness: 62.0,
185 outline: vec![
186 Point {
187 loop_label: 0,
188 x: 5030.5,
189 y: -120.0,
190 angle: 0.0,
191 },
192 Point {
193 loop_label: 1,
194 x: 3000.0,
195 y: 2350.0,
196 angle: 360.0,
197 },
198 ],
199 };
200
201 let routing_outlines = vec![RoutingOutline {
202 owner: "ECAD".to_string(),
203 routing_layers: "ALL".to_string(),
204 outline: vec![
205 Point {
206 loop_label: 0,
207 x: 5112.5,
208 y: 150.0,
209 angle: 0.0,
210 },
211 Point {
212 loop_label: 0,
213 x: 5112.5,
214 y: 150.0,
215 angle: 0.0,
216 },
217 ],
218 }];
219
220 let placement_outlines = vec![
221 PlacementOutline {
222 owner: "MCAD".to_string(),
223 board_side: "TOP".to_string(),
224 outline_height: 1000.0,
225 outline: vec![
226 Point {
227 loop_label: 0,
228 x: 5080.0,
229 y: 2034.9,
230 angle: 0.0,
231 },
232 Point {
233 loop_label: 0,
234 x: 5080.0,
235 y: 2034.9,
236 angle: 0.0,
237 },
238 ],
239 },
240 PlacementOutline {
241 owner: "UNOWNED".to_string(),
242 board_side: "BOTTOM".to_string(),
243 outline_height: 200.0,
244 outline: vec![
245 Point {
246 loop_label: 0,
247 x: 300.0,
248 y: 200.0,
249 angle: 0.0,
250 },
251 Point {
252 loop_label: 0,
253 x: 4800.0,
254 y: 200.0,
255 angle: 0.0,
256 },
257 ],
258 },
259 ];
260
261 let routing_keepouts = vec![RoutingKeepout {
262 owner: "ECAD".to_string(),
263 routing_layers: "ALL".to_string(),
264 outline: vec![
265 Point {
266 loop_label: 0,
267 x: 2650.0,
268 y: 2350.0,
269 angle: 0.0,
270 },
271 Point {
272 loop_label: 0,
273 x: 3100.0,
274 y: 2350.0,
275 angle: 360.0,
276 },
277 ],
278 }];
279
280 let placement_keepouts = vec![
281 PlacementKeepout {
282 owner: "MCAD".to_string(),
283 board_side: "BOTH".to_string(),
284 keepout_height: 0.0,
285 outline: vec![
286 Point {
287 loop_label: 0,
288 x: 2650.0,
289 y: 2350.0,
290 angle: 0.0,
291 },
292 Point {
293 loop_label: 0,
294 x: 3100.0,
295 y: 2350.0,
296 angle: 360.0,
297 },
298 ],
299 },
300 PlacementKeepout {
301 owner: "MCAD".to_string(),
302 board_side: "TOP".to_string(),
303 keepout_height: 300.0,
304 outline: vec![
305 Point {
306 loop_label: 0,
307 x: 3700.0,
308 y: 5000.0,
309 angle: 0.0,
310 },
311 Point {
312 loop_label: 0,
313 x: 3700.0,
314 y: 5000.0,
315 angle: 0.0,
316 },
317 ],
318 },
319 ];
320
321 let drilled_holes = vec![
322 Hole {
323 diameter: 30.0,
324 x: 1800.0,
325 y: 100.0,
326 plating_style: "PTH".to_string(),
327 associated_part: "J1".to_string(),
328 hole_type: "PIN".to_string(),
329 owner: "ECAD".to_string(),
330 },
331 Hole {
332 diameter: 20.0,
333 x: 2000.0,
334 y: 1600.0,
335 plating_style: "PTH".to_string(),
336 associated_part: "BOARD".to_string(),
337 hole_type: "VIA".to_string(),
338 owner: "ECAD".to_string(),
339 },
340 Hole {
341 diameter: 93.0,
342 x: 5075.0,
343 y: 0.0,
344 plating_style: "PTH".to_string(),
345 associated_part: "BOARD".to_string(),
346 hole_type: "MTG".to_string(),
347 owner: "UNOWNED".to_string(),
348 },
349 Hole {
350 diameter: 93.0,
351 x: 0.0,
352 y: 4800.0,
353 plating_style: "NPTH".to_string(),
354 associated_part: "BOARD".to_string(),
355 hole_type: "TOOL".to_string(),
356 owner: "MCAD".to_string(),
357 },
358 ];
359
360 let notes = vec![Note {
361 x: 1800.0,
362 y: 300.0,
363 text_height: 75.0,
364 test_string_physical_length: 1700.0,
365 text: "Do not move connectors!".to_string(),
366 }];
367
368 let component_placements = vec![
369 ComponentPlacement {
370 package_name: "cs13_a".to_string(),
371 part_number: "pn-cap".to_string(),
372 reference_designator: "C1".to_string(),
373 x: 4000.0,
374 y: 1000.0,
375 mounting_offset: 100.0,
376 rotation_angle: 0.0,
377 board_side: "TOP".to_string(),
378 placement_status: "PLACED".to_string(),
379 },
380 ComponentPlacement {
381 package_name: "cc1210".to_string(),
382 part_number: "pn-cc1210".to_string(),
383 reference_designator: "C2".to_string(),
384 x: 3000.0,
385 y: 3500.0,
386 mounting_offset: 0.0,
387 rotation_angle: 0.0,
388 board_side: "TOP".to_string(),
389 placement_status: "PLACED".to_string(),
390 },
391 ComponentPlacement {
392 package_name: "cc1210".to_string(),
393 part_number: "pn-cc1210".to_string(),
394 reference_designator: "C3".to_string(),
395 x: 3200.0,
396 y: 1800.0,
397 mounting_offset: 0.0,
398 rotation_angle: 0.0,
399 board_side: "BOTTOM".to_string(),
400 placement_status: "PLACED".to_string(),
401 },
402 ];
403
404 let expected_board = BoardPanel {
405 header,
406 outline,
407 other_outlines: vec![],
408 routing_outlines,
409 placement_outlines,
410 routing_keepouts,
411 via_keepouts: vec![],
412 placement_keepouts,
413 placement_group_areas: vec![],
414 drilled_holes,
415 notes,
416 component_placements,
417 };
418
419 let board = parse_board_or_panel(input).unwrap();
420 assert_eq!(board, expected_board);
421 }
422 #[test]
423 fn test_parse_panel() {
424 let input = ".HEADER
425PANEL_FILE 3.0 \"Sample File Generator\" 10/22/96.16:20:19 1
426sample_panel THOU
427.END_HEADER
428.PANEL_OUTLINE MCAD
42962.0
4300 0.0 0.0 0.0
4310 16000.0 0.0 0.0
432.END_PANEL_OUTLINE
433.PLACE_KEEPOUT MCAD
434BOTTOM 0.0
4350 13500.0 0.0 0.0
4360 13500.0 12000.0 0.0
4370 13500.0 0.0 0.0
438.END_PLACE_KEEPOUT
439.PLACE_KEEPOUT MCAD
440BOTTOM 0.0
4410 0.0 0.0 0.0
4420 2200.0 0.0 0.0
4430 2200.0 12000.0 0.0
4440 0.0 12000.0 0.0
4450 0.0 0.0 0.0
446.END_PLACE_KEEPOUT
447.DRILLED_HOLES
448250.0 15500.0 11500.0 NPTH PANEL TOOL MCAD
449250.0 500.0 500.0 NPTH PANEL TOOL MCAD
450.END_DRILLED_HOLES
451.PLACEMENT
452sample_board pn-board BOARD
4531700.0 3300.0 0.0 0.0 TOP MCAD
454.END_PLACEMENT";
455
456 let header = BoardPanelHeader {
457 file_type: "PANEL_FILE".to_string(),
458 version: 3,
459 system_id: "Sample File Generator".to_string(),
460 date: "10/22/96.16:20:19".to_string(),
461 file_version: 1,
462 board_name: "sample_panel".to_string(),
463 units: "THOU".to_string(),
464 };
465
466 let outline = BoardPanelOutline {
467 owner: "MCAD".to_string(),
468 thickness: 62.0,
469 outline: vec![
470 Point {
471 loop_label: 0,
472 x: 0.0,
473 y: 0.0,
474 angle: 0.0,
475 },
476 Point {
477 loop_label: 0,
478 x: 16000.0,
479 y: 0.0,
480 angle: 0.0,
481 },
482 ],
483 };
484
485 let placement_keepouts = vec![
486 PlacementKeepout {
487 owner: "MCAD".to_string(),
488 board_side: "BOTTOM".to_string(),
489 keepout_height: 0.0,
490 outline: vec![
491 Point {
492 loop_label: 0,
493 x: 13500.0,
494 y: 0.0,
495 angle: 0.0,
496 },
497 Point {
498 loop_label: 0,
499 x: 13500.0,
500 y: 12000.0,
501 angle: 0.0,
502 },
503 Point {
504 loop_label: 0,
505 x: 13500.0,
506 y: 0.0,
507 angle: 0.0,
508 },
509 ],
510 },
511 PlacementKeepout {
512 owner: "MCAD".to_string(),
513 board_side: "BOTTOM".to_string(),
514 keepout_height: 0.0,
515 outline: vec![
516 Point {
517 loop_label: 0,
518 x: 0.0,
519 y: 0.0,
520 angle: 0.0,
521 },
522 Point {
523 loop_label: 0,
524 x: 2200.0,
525 y: 0.0,
526 angle: 0.0,
527 },
528 Point {
529 loop_label: 0,
530 x: 2200.0,
531 y: 12000.0,
532 angle: 0.0,
533 },
534 Point {
535 loop_label: 0,
536 x: 0.0,
537 y: 12000.0,
538 angle: 0.0,
539 },
540 Point {
541 loop_label: 0,
542 x: 0.0,
543 y: 0.0,
544 angle: 0.0,
545 },
546 ],
547 },
548 ];
549
550 let drilled_holes = vec![
551 Hole {
552 diameter: 250.0,
553 x: 15500.0,
554 y: 11500.0,
555 plating_style: "NPTH".to_string(),
556 associated_part: "PANEL".to_string(),
557 hole_type: "TOOL".to_string(),
558 owner: "MCAD".to_string(),
559 },
560 Hole {
561 diameter: 250.0,
562 x: 500.0,
563 y: 500.0,
564 plating_style: "NPTH".to_string(),
565 associated_part: "PANEL".to_string(),
566 hole_type: "TOOL".to_string(),
567 owner: "MCAD".to_string(),
568 },
569 ];
570
571 let component_placements = vec![ComponentPlacement {
572 package_name: "sample_board".to_string(),
573 part_number: "pn-board".to_string(),
574 reference_designator: "BOARD".to_string(),
575 x: 1700.0,
576 y: 3300.0,
577 mounting_offset: 0.0,
578 rotation_angle: 0.0,
579 board_side: "TOP".to_string(),
580 placement_status: "MCAD".to_string(),
581 }];
582
583 let expected_panel = BoardPanel {
584 header,
585 outline,
586 other_outlines: vec![],
587 routing_outlines: vec![],
588 placement_outlines: vec![],
589 routing_keepouts: vec![],
590 via_keepouts: vec![],
591 placement_keepouts,
592 placement_group_areas: vec![],
593 drilled_holes,
594 notes: vec![],
595 component_placements,
596 };
597
598 let panel = parse_board_or_panel(input).unwrap();
599 assert_eq!(panel, expected_panel);
600 }
601}