idf_parser/
component_placement.rs

1use nom::branch::alt;
2
3use crate::primitives::ws;
4use crate::{parse_section, ws_separated};
5use nom::IResult;
6use nom::Parser;
7use nom::bytes::complete::{is_not, tag};
8use nom::character::complete::not_line_ending;
9use nom::multi::many0;
10use nom::number::complete::float;
11use nom::sequence::delimited;
12
13/// Represents a component placement in the IDF format.
14#[derive(Debug, PartialEq, Clone, Default, PartialOrd)]
15pub struct ComponentPlacement {
16    pub package_name: String,
17    pub part_number: String,
18    pub reference_designator: String, // Any (Component instance ref designator), NOREFDES, BOARD
19    pub x: f32,
20    pub y: f32,
21    pub mounting_offset: f32,     // >= 0 Mounting offset from board surface
22    pub rotation_angle: f32,      // degrees
23    pub board_side: String,       // "TOP" or "BOTTOM"
24    pub placement_status: String, // "PLACED", "UNPLACED", "ECAD", "MCAD"
25}
26
27/// Parses a single component placement from the input string.
28/// http://www.aertia.com/docs/priware/IDF_V30_Spec.pdf#page=27
29///
30/// # Example
31///
32/// ```
33/// use idf_parser::component_placement::{component_placement, ComponentPlacement};
34/// let input = "cs13_a pn-cap C1\n4000.0 1000.0 100.0 0.0 TOP PLACED";
35/// let (remaining, component_placement) = component_placement(input).unwrap();
36/// ```
37pub fn component_placement(input: &str) -> IResult<&str, ComponentPlacement> {
38    let (
39        remaining,
40        (
41            package_name,
42            part_number,
43            reference_designator,
44            x,
45            y,
46            mounting_offset,
47            rotation_angle,
48            board_side,
49            placement_status,
50        ),
51    ) = ws_separated!((
52        is_not(" "),                                                      // package name
53        is_not(" "),                                                      // part number
54        not_line_ending,                                                  // reference designator
55        float,                                                            // x coordinate
56        float,                                                            // y coordinate
57        float,                                                            // mounting offset
58        float,                                                            // rotation
59        alt((tag("TOP"), tag("BOTTOM"))),                                 // board side
60        alt((tag("PLACED"), tag("UNPLACED"), tag("ECAD"), tag("MCAD"),))  // placement status
61    ))
62    .parse(input)?;
63
64    let component_placement = ComponentPlacement {
65        package_name: package_name.to_string(),
66        part_number: part_number.to_string(),
67        reference_designator: reference_designator.to_string(),
68        x,
69        y,
70        mounting_offset,
71        rotation_angle,
72        board_side: board_side.to_string(),
73        placement_status: placement_status.to_string(),
74    };
75
76    Ok((remaining, component_placement))
77}
78
79/// Parses a section of component placements from the input string.
80/// http://www.aertia.com/docs/priware/IDF_V30_Spec.pdf#page=27
81///
82/// # Example
83///
84/// ```
85/// use idf_parser::component_placement::{parse_component_placement_section, ComponentPlacement};
86/// let input = ".PLACEMENT
87/// cs13_a pn-cap C1
88/// 4000.0 1000.0 100.0 0.0 TOP PLACED
89/// .END_PLACEMENT";
90///
91/// let (remaining, component_placements) = parse_component_placement_section(input).unwrap();
92/// ```
93pub fn parse_component_placement_section(input: &str) -> IResult<&str, Vec<ComponentPlacement>> {
94    parse_section!("PLACEMENT", ws(many0(component_placement))).parse(input)
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100    #[test]
101    fn test_component_placement() {
102        let input = "cs13_a pn-cap C1\n4000.0 1000.0 100.0 0.0 TOP PLACED";
103        let expected = ComponentPlacement {
104            package_name: "cs13_a".to_string(),
105            part_number: "pn-cap".to_string(),
106            reference_designator: "C1".to_string(),
107            x: 4000.0,
108            y: 1000.0,
109            mounting_offset: 100.0,
110            rotation_angle: 0.0,
111            board_side: "TOP".to_string(),
112            placement_status: "PLACED".to_string(),
113        };
114        let result = component_placement(input);
115        // assert!(result.is_ok());
116        let (_, component_placement) = result.unwrap();
117
118        assert_eq!(component_placement, expected);
119    }
120    #[test]
121    fn test_component_placement_section() {
122        let input = ".PLACEMENT
123cs13_a pn-cap C1
1244000.0 1000.0 100.0 0.0 TOP PLACED
125cc1210 pn-cc1210 C2
1263000.0 3500.0 0.0 0.0 TOP UNPLACED
127cc1210 pn-cc1210 C3
1283200.0 1800.0 0.0 0.0 BOTTOM MCAD
129dip_14w pn-hs346-dip U4
1302200.0 2500.0 0.0 270.0 TOP ECAD
131.END_PLACEMENT";
132
133        let expected = vec![
134            ComponentPlacement {
135                package_name: "cs13_a".to_string(),
136                part_number: "pn-cap".to_string(),
137                reference_designator: "C1".to_string(),
138                x: 4000.0,
139                y: 1000.0,
140                mounting_offset: 100.0,
141                rotation_angle: 0.0,
142                board_side: "TOP".to_string(),
143                placement_status: "PLACED".to_string(),
144            },
145            ComponentPlacement {
146                package_name: "cc1210".to_string(),
147                part_number: "pn-cc1210".to_string(),
148                reference_designator: "C2".to_string(),
149                x: 3000.0,
150                y: 3500.0,
151                mounting_offset: 0.0,
152                rotation_angle: 0.0,
153                board_side: "TOP".to_string(),
154                placement_status: "UNPLACED".to_string(),
155            },
156            ComponentPlacement {
157                package_name: "cc1210".to_string(),
158                part_number: "pn-cc1210".to_string(),
159                reference_designator: "C3".to_string(),
160                x: 3200.0,
161                y: 1800.0,
162                mounting_offset: 0.0,
163                rotation_angle: 0.0,
164                board_side: "BOTTOM".to_string(),
165                placement_status: "MCAD".to_string(),
166            },
167            ComponentPlacement {
168                package_name: "dip_14w".to_string(),
169                part_number: "pn-hs346-dip".to_string(),
170                reference_designator: "U4".to_string(),
171                x: 2200.0,
172                y: 2500.0,
173                mounting_offset: 0.0,
174                rotation_angle: 270.0,
175                board_side: "TOP".to_string(),
176                placement_status: "ECAD".to_string(),
177            },
178        ];
179
180        let (remaining, component_placements) = parse_component_placement_section(input).unwrap();
181        assert_eq!(remaining, "");
182        assert_eq!(component_placements, expected);
183    }
184    #[test]
185    fn test_invalid_component_placement() {
186        // Invalid input with extra text
187        let input = "cs13_a pn-cap C1\n4000.0 1000.0 100.0 hi hi 0.0 TOP PLACED\n";
188        let result = component_placement(input);
189        assert!(!result.is_ok());
190    }
191}