idf_parser/
outlines.rs

1use nom::branch::alt;
2use nom::sequence::delimited;
3
4use crate::point::{Point, point};
5use crate::primitives::ws;
6use crate::{parse_section, ws_separated};
7use nom::IResult;
8use nom::Parser;
9use nom::bytes::complete::{is_not, tag};
10use nom::character::complete::not_line_ending;
11use nom::multi::many1;
12use nom::number::complete::float;
13
14/// Board/panel outline.
15/// http://www.aertia.com/docs/priware/IDF_V30_Spec.pdf#page=10
16///
17/// This section defines the board or panel outline and its internal cutouts as a 2D profile with
18/// thickness. The outline and cutouts consist of simple closed curves made up of arcs and lines.
19/// Only one outline may be specified, but multiple cutouts are allowed.
20#[derive(Clone, Debug, PartialEq, Default, PartialOrd)]
21pub struct BoardPanelOutline {
22    pub owner: String, // MCAD, ECAD or UNOWNED
23    pub thickness: f32,
24    pub outline: Vec<Point>,
25}
26
27pub fn parse_board_panel_outline(input: &str) -> IResult<&str, BoardPanelOutline> {
28    fn interior_contents(input: &str) -> IResult<&str, (&str, f32, Vec<Point>)> {
29        (ws(owner), ws(float), ws(many1(ws(point)))).parse(input)
30    }
31
32    let (remaining, (owner, thickness, outline)) = ws(alt((
33        parse_section!("BOARD_OUTLINE", interior_contents),
34        parse_section!("PANEL_OUTLINE", interior_contents),
35    )))
36    .parse(input)?;
37
38    Ok((
39        remaining,
40        BoardPanelOutline {
41            owner: owner.to_string(),
42            thickness,
43            outline,
44        },
45    ))
46}
47
48/// Other outline.
49/// http://www.aertia.com/docs/priware/IDF_V30_Spec.pdf#page=11
50///
51/// This section defines an additional outline with cutouts that can be used for other purposes than
52/// the board outline such as for defining a heatsink or board core. The outline and cutouts consist of
53/// simple closed curves made up of arcs and lines. Multiple other outline sections may be specified.
54#[derive(Debug, PartialEq, Clone, Default, PartialOrd)]
55pub struct OtherOutline {
56    pub owner: String, // MCAD, ECAD or UNOWNED
57    pub id: String,
58    pub extrude_thickness: f32,
59    pub board_side: String, // TOP or BOTTOM
60    pub outline: Vec<Point>,
61}
62
63pub fn parse_other_outline(input: &str) -> IResult<&str, OtherOutline> {
64    let (remaining, (owner, id, extrude_thickness, board_side, outline)) = parse_section!(
65        "OTHER_OUTLINE",
66        ws_separated!((
67            owner,            // owner
68            is_not(" "),      // ID
69            float,            // extrude_thickness
70            not_line_ending,  // board_side
71            many1(ws(point))  // outline
72        ))
73    )
74    .parse(input)?;
75
76    Ok((
77        remaining,
78        OtherOutline {
79            owner: owner.to_string(),
80            id: id.to_string(),
81            extrude_thickness,
82            board_side: board_side.to_string(),
83            outline,
84        },
85    ))
86}
87
88/// Routing outline.
89/// http://www.aertia.com/docs/priware/IDF_V30_Spec.pdf#page=14
90///
91/// This section defines a routing outline for the board or panel. Each routing outline specifies a
92/// region within which routing must be confined, and consists of a simple closed curve made up of
93/// arcs and lines. Portions of routing outlines on a panel that lie on a board in the panel are inherited
94/// by that board. Multiple routing outlines may be defined.
95#[derive(Debug, PartialEq, Clone, Default, PartialOrd)]
96pub struct RoutingOutline {
97    pub owner: String,          // MCAD, ECAD or UNOWNED
98    pub routing_layers: String, // TOP, BOTTOM, BOTH, INNER or ALL
99    pub outline: Vec<Point>,
100}
101
102pub fn parse_routing_outline(input: &str) -> IResult<&str, RoutingOutline> {
103    let (remaining, (owner, routing_layers, outline)) = parse_section!(
104        "ROUTE_OUTLINE",
105        ws_separated!((owner, not_line_ending, many1(ws(point))))
106    )
107    .parse(input)?;
108
109    Ok((
110        remaining,
111        RoutingOutline {
112            owner: owner.to_string(),
113            routing_layers: routing_layers.to_string(),
114            outline,
115        },
116    ))
117}
118
119/// Placement outline.
120/// http://www.aertia.com/docs/priware/IDF_V30_Spec.pdf#page=16
121///
122/// This section defines a placement outline for the board or panel. Each placement outline specifies
123/// a region within which components must be placed, and consists of a simple closed curve made up
124/// of arcs and lines plus a height restriction. Portions of placement outlines on a panel that lie on a
125/// board in the panel are inherited by that board. Multiple placement outlines may be defined.
126#[derive(Debug, PartialEq, Clone, Default, PartialOrd)]
127pub struct PlacementOutline {
128    pub owner: String,       // MCAD, ECAD or UNOWNED
129    pub board_side: String,  // TOP, BOTTOM or BOTH
130    pub outline_height: f32, // Any (≥ 0)
131    pub outline: Vec<Point>,
132}
133
134pub fn parse_placement_outline(input: &str) -> IResult<&str, PlacementOutline> {
135    let (remaining, (owner, board_side, outline_height, outline)) = parse_section!(
136        "PLACE_OUTLINE",
137        ws_separated!((
138            owner,            // owner
139            is_not(" "),      // board_side
140            float,            // outline_height
141            many1(ws(point))  // outline
142        ))
143    )
144    .parse(input)?;
145
146    Ok((
147        remaining,
148        PlacementOutline {
149            owner: owner.to_string(),
150            board_side: board_side.to_string(),
151            outline_height,
152            outline,
153        },
154    ))
155}
156
157/// Routing keepout.
158/// http://www.aertia.com/docs/priware/IDF_V30_Spec.pdf#page=18
159///
160/// This section defines a routing keepout for the board or panel. Routing keepouts specify regions
161/// where routing is not allowed. Routing keepouts can exist on top, bottom, both top and bottom,
162/// or all routing layers. Each keepout consists of a simple closed curve made up of arcs and lines.
163/// Portions of routing keepouts on a panel that lie on a board in the panel are inherited by that board.
164/// Multiple keepouts are allowed.
165#[derive(Debug, PartialEq, Clone, Default, PartialOrd)]
166pub struct RoutingKeepout {
167    pub owner: String,          // MCAD, ECAD or UNOWNED
168    pub routing_layers: String, // TOP, BOTTOM, BOTH, INNER or ALL
169    pub outline: Vec<Point>,
170}
171
172pub fn parse_routing_keepout(input: &str) -> IResult<&str, RoutingKeepout> {
173    let (remaining, (owner, routing_layers, outline)) = parse_section!(
174        "ROUTE_KEEPOUT",
175        ws_separated!((owner, not_line_ending, many1(ws(point))))
176    )
177    .parse(input)?;
178
179    Ok((
180        remaining,
181        RoutingKeepout {
182            owner: owner.to_string(),
183            routing_layers: routing_layers.to_string(),
184            outline,
185        },
186    ))
187}
188
189/// Via keepout.
190/// http://www.aertia.com/docs/priware/IDF_V30_Spec.pdf#page=20
191///
192/// This section defines a via keepout for the board or panel. Via keepouts specify regions where vias
193/// are not allowed (although routing is still allowed). Each keepout consists of a simple closed curve
194/// made up of arcs and lines. Portions of via keepouts on a panel that lie on a board in the panel are
195/// inherited by that board. Multiple via keepouts are allowed. Only through vias (vias that go all the
196/// way through the board) are supported.
197#[derive(Debug, PartialEq, Clone, Default, PartialOrd)]
198pub struct ViaKeepout {
199    pub owner: String, // MCAD, ECAD or UNOWNED
200    pub outline: Vec<Point>,
201}
202
203pub fn parse_via_keepout(input: &str) -> IResult<&str, ViaKeepout> {
204    let (remaining, (owner, outline)) =
205        parse_section!("VIA_KEEPOUT", ws_separated!((owner, many1(ws(point))))).parse(input)?;
206
207    Ok((
208        remaining,
209        ViaKeepout {
210            owner: owner.to_string(),
211            outline,
212        },
213    ))
214}
215
216/// Placement keepout.
217/// http://www.aertia.com/docs/priware/IDF_V30_Spec.pdf#page=21
218///
219/// This section defines a placement keepout for the board or panel. Placement keepouts specify
220/// regions on the board where components cannot be placed. A keepout can apply to all
221/// components, or to only those components above a specified height. Placement keepouts can exist
222/// on the top, bottom, or both top and bottom of the board or panel. Each keepout consists of a
223/// simple closed curve made up of arcs and lines along with a height restriction. Portions of
224/// placement keepouts on a panel that lie on a board in the panel are inherited by that board.
225/// Multiple keepouts are allowed.
226#[derive(Debug, PartialEq, Clone, Default, PartialOrd)]
227pub struct PlacementKeepout {
228    pub owner: String,       // MCAD, ECAD or UNOWNED
229    pub board_side: String,  // TOP, BOTTOM or BOTH
230    pub keepout_height: f32, // Any (≥ 0)
231    pub outline: Vec<Point>,
232}
233
234pub fn parse_placement_keepout(input: &str) -> IResult<&str, PlacementKeepout> {
235    let (remaining, (owner, board_side, keepout_height, outline)) = parse_section!(
236        "PLACE_KEEPOUT",
237        ws_separated!((
238            owner,
239            is_not(" "),      // board_side
240            float,            // keepout_height
241            many1(ws(point))  // outline
242        ))
243    )
244    .parse(input)?;
245
246    Ok((
247        remaining,
248        PlacementKeepout {
249            owner: owner.to_string(),
250            board_side: board_side.to_string(),
251            keepout_height,
252            outline,
253        },
254    ))
255}
256
257/// Placement group area.
258/// http://www.aertia.com/docs/priware/IDF_V30_Spec.pdf#page=23
259///
260/// This section specifies an area where a group of related components is to be placed. For example,
261/// it may be desirable to place all analog components in a particular area for thermal considerations.
262/// Each placement group area consists of a simple closed curve made up of arcs and lines along with
263/// a name designating the group of components to be placed in that area. Multiple areas are
264/// allowed.
265#[derive(Debug, PartialEq, Clone, Default, PartialOrd)]
266pub struct PlacementGroupArea {
267    pub owner: String,      // MCAD, ECAD or UNOWNED
268    pub board_side: String, // TOP, BOTTOM or BOTH
269    pub group_name: String,
270    pub outline: Vec<Point>,
271}
272
273pub fn parse_placement_group_area(input: &str) -> IResult<&str, PlacementGroupArea> {
274    let (remaining, (owner, board_side, group_name, outline)) = parse_section!(
275        "PLACE_REGION",
276        ws_separated!((
277            owner,
278            is_not(" "),      // board_side
279            not_line_ending,  // group_name
280            many1(ws(point))  // outline
281        ))
282    )
283    .parse(input)?;
284
285    Ok((
286        remaining,
287        PlacementGroupArea {
288            owner: owner.to_string(),
289            board_side: board_side.to_string(),
290            group_name: group_name.to_string(),
291            outline,
292        },
293    ))
294}
295
296/// Determine the owner of an outline or set of holes.
297pub fn owner(input: &str) -> IResult<&str, &str> {
298    alt((tag("ECAD"), tag("MCAD"), tag("UNOWNED"))).parse(input)
299}
300
301#[cfg(test)]
302mod tests {
303    use super::*;
304    #[test]
305    fn test_parse_board_outline() {
306        let input = ".BOARD_OUTLINE MCAD
30762.0
3080 5.5 -120.0 0.0
3090 36.1 -120.0 263.266
3101 5127.5 56 360
311.END_BOARD_OUTLINE";
312
313        let (remaining, board_outline) = parse_board_panel_outline(input).unwrap();
314        assert_eq!(remaining, "");
315        assert_eq!(board_outline.owner, "MCAD");
316        assert_eq!(board_outline.thickness, 62.0);
317        assert_eq!(board_outline.outline.len(), 3);
318        assert_eq!(board_outline.outline[0].loop_label, 0);
319        assert_eq!(board_outline.outline[0].x, 5.5);
320        assert_eq!(board_outline.outline[0].y, -120.0);
321        assert_eq!(board_outline.outline[0].angle, 0.0);
322    }
323
324    #[test]
325    fn test_parse_board_outline_2() {
326        let input = ".BOARD_OUTLINE  ECAD
327      40.0
3280         0.0         0.0       0.000
3293      1574.0       235.7      90.087
330.END_BOARD_OUTLINE";
331        let (remaining, board_outline) = parse_board_panel_outline(input).unwrap();
332
333        let expected_outline = BoardPanelOutline {
334            owner: "ECAD".to_string(),
335            thickness: 40.0,
336            outline: vec![
337                Point {
338                    loop_label: 0,
339                    x: 0.0,
340                    y: 0.0,
341                    angle: 0.0,
342                },
343                Point {
344                    loop_label: 3,
345                    x: 1574.0,
346                    y: 235.7,
347                    angle: 90.087,
348                },
349            ],
350        };
351
352        assert_eq!(remaining, "");
353        assert_eq!(board_outline, expected_outline);
354    }
355
356    #[test]
357    fn test_parse_other_outline() {
358        let input = ".OTHER_OUTLINE MCAD
359my_outline 62.0 TOP
3600 5.5 -120.0 0.0
361.END_OTHER_OUTLINE";
362
363        let (remaining, other_outline) = parse_other_outline(input).unwrap();
364        assert_eq!(remaining, "");
365        assert_eq!(other_outline.owner, "MCAD");
366        assert_eq!(other_outline.id, "my_outline");
367        assert_eq!(other_outline.extrude_thickness, 62.0);
368        assert_eq!(other_outline.board_side, "TOP");
369        assert_eq!(other_outline.outline.len(), 1);
370        assert_eq!(other_outline.outline[0].loop_label, 0);
371        assert_eq!(other_outline.outline[0].x, 5.5);
372        assert_eq!(other_outline.outline[0].y, -120.0);
373        assert_eq!(other_outline.outline[0].angle, 0.0);
374    }
375
376    #[test]
377    fn test_parse_routing_outline() {
378        let input = ".ROUTE_OUTLINE ECAD
379ALL
3800 5112.5 150.0 0.0
3810 5112.5 2058.2 0.0
382.END_ROUTE_OUTLINE";
383
384        let (remaining, routing_outline) = parse_routing_outline(input).unwrap();
385        assert_eq!(remaining, "");
386        assert_eq!(routing_outline.owner, "ECAD");
387        assert_eq!(routing_outline.routing_layers, "ALL");
388        assert_eq!(routing_outline.outline.len(), 2);
389        assert_eq!(routing_outline.outline[0].loop_label, 0);
390        assert_eq!(routing_outline.outline[0].x, 5112.5);
391        assert_eq!(routing_outline.outline[0].y, 150.0);
392        assert_eq!(routing_outline.outline[0].angle, 0.0);
393    }
394
395    #[test]
396    fn test_parse_placement_outline() {
397        let input = ".PLACE_OUTLINE MCAD
398TOP 1000.0
3990 -5.0 2034.9 -152.9
400.END_PLACE_OUTLINE";
401
402        let (remaining, placement_outline) = parse_placement_outline(input).unwrap();
403        assert_eq!(remaining, "");
404        assert_eq!(placement_outline.owner, "MCAD");
405        assert_eq!(placement_outline.board_side, "TOP");
406        assert_eq!(placement_outline.outline_height, 1000.0);
407        assert_eq!(placement_outline.outline.len(), 1);
408        assert_eq!(placement_outline.outline[0].loop_label, 0);
409        assert_eq!(placement_outline.outline[0].x, -5.0);
410        assert_eq!(placement_outline.outline[0].y, 2034.9);
411        assert_eq!(placement_outline.outline[0].angle, -152.9);
412    }
413
414    #[test]
415    fn test_parse_routing_keepout() {
416        let input = ".ROUTE_KEEPOUT ECAD
417ALL
4180 2650.0 2350.0 0.0
4190 3100.0 2350.0 360.0
420.END_ROUTE_KEEPOUT";
421
422        let (remaining, routing_keepout) = parse_routing_keepout(input).unwrap();
423        assert_eq!(remaining, "");
424        assert_eq!(routing_keepout.owner, "ECAD");
425        assert_eq!(routing_keepout.routing_layers, "ALL");
426        assert_eq!(routing_keepout.outline.len(), 2);
427        assert_eq!(routing_keepout.outline[0].loop_label, 0);
428        assert_eq!(routing_keepout.outline[0].x, 2650.0);
429        assert_eq!(routing_keepout.outline[0].y, 2350.0);
430        assert_eq!(routing_keepout.outline[0].angle, 0.0);
431    }
432    #[test]
433    fn test_parse_via_keepout() {
434        let input = ".VIA_KEEPOUT ECAD
4350 2650.0 2350.0 0.0
4360 3100.0 2350.0 360.0
437.END_VIA_KEEPOUT";
438
439        let (remaining, via_keepout) = parse_via_keepout(input).unwrap();
440        assert_eq!(remaining, "");
441        assert_eq!(via_keepout.owner, "ECAD");
442        assert_eq!(via_keepout.outline.len(), 2);
443        assert_eq!(via_keepout.outline[0].loop_label, 0);
444        assert_eq!(via_keepout.outline[0].x, 2650.0);
445        assert_eq!(via_keepout.outline[0].y, 2350.0);
446        assert_eq!(via_keepout.outline[0].angle, 0.0);
447    }
448
449    #[test]
450    fn test_parse_placement_keepout() {
451        let input = ".PLACE_KEEPOUT MCAD
452TOP 300.0
4530 3700.0 5000.0 0.0
4540 3700.0 5000.0 0.0
455.END_PLACE_KEEPOUT";
456
457        let (remaining, placement_keepout) = parse_placement_keepout(input).unwrap();
458        assert_eq!(remaining, "");
459        assert_eq!(placement_keepout.owner, "MCAD");
460        assert_eq!(placement_keepout.board_side, "TOP");
461        assert_eq!(placement_keepout.keepout_height, 300.0);
462        assert_eq!(placement_keepout.outline.len(), 2);
463        assert_eq!(placement_keepout.outline[0].loop_label, 0);
464        assert_eq!(placement_keepout.outline[0].x, 3700.0);
465        assert_eq!(placement_keepout.outline[0].y, 5000.0);
466        assert_eq!(placement_keepout.outline[0].angle, 0.0);
467    }
468
469    #[test]
470    fn test_parse_placement_group_area() {
471        let input = ".PLACE_REGION UNOWNED
472TOP the_best_group
4730 5.5 -120.0 0.0
474.END_PLACE_REGION";
475
476        let (remaining, placement_group_area) = parse_placement_group_area(input).unwrap();
477        assert_eq!(remaining, "");
478        assert_eq!(placement_group_area.owner, "UNOWNED");
479        assert_eq!(placement_group_area.board_side, "TOP");
480        assert_eq!(placement_group_area.group_name, "the_best_group");
481        assert_eq!(placement_group_area.outline.len(), 1);
482        assert_eq!(placement_group_area.outline[0].loop_label, 0);
483        assert_eq!(placement_group_area.outline[0].x, 5.5);
484        assert_eq!(placement_group_area.outline[0].y, -120.0);
485        assert_eq!(placement_group_area.outline[0].angle, 0.0);
486    }
487
488    #[test]
489    fn test_point() {
490        let input = "0 5.5 -120.0 0.0";
491        let (remaining, point) = point(input).unwrap();
492
493        assert_eq!(remaining, "");
494        assert_eq!(point.loop_label, 0);
495        assert_eq!(point.x, 5.5);
496        assert_eq!(point.y, -120.0);
497        assert_eq!(point.angle, 0.0);
498    }
499
500    #[test]
501    fn test_owner() {
502        let input = "ECADMCADUNOWNED";
503        let (remaining, owner_str) = owner(input).unwrap();
504        assert_eq!(remaining, "MCADUNOWNED");
505        assert_eq!(owner_str, "ECAD");
506
507        let (remaining, owner_str) = owner(remaining).unwrap();
508        assert_eq!(remaining, "UNOWNED");
509        assert_eq!(owner_str, "MCAD");
510
511        let (remaining, owner_str) = owner(remaining).unwrap();
512        assert_eq!(remaining, "");
513        assert_eq!(owner_str, "UNOWNED");
514    }
515}