idf_parser/
components.rs

1use crate::point::{Point, point};
2use crate::primitives::{quote_string, ws};
3use crate::{parse_section, ws_separated};
4use nom::IResult;
5use nom::Parser;
6use nom::branch::alt;
7use nom::bytes::complete::{is_not, tag};
8use nom::multi::many0;
9use nom::number::complete::float;
10use nom::sequence::delimited;
11use std::collections::HashMap;
12
13/// Parses the properties of an electrical component from the input string.
14///
15/// Represents the properties of an electrical component.
16/// http://www.aertia.com/docs/priware/IDF_V30_Spec.pdf#page=33
17///
18/// Properties of an electrical component:
19/// - capacitance: Capacitance in microfarads
20/// - resistance: Resistance in ohms
21/// - tolerance: Percent deviation
22/// - power_opr: Operating power rating in milliwatts
23/// - power_max: Maximum power rating in milliwatts
24/// - thermal_cond: Thermal conductivity in watts per meter °C
25/// - theta_jb: Junction to board thermal resistance in °C per watt
26/// - theta_jc: Junction to case thermal resistance in °C per watt
27/// - other: User-defined properties
28fn electrical_properties(input: &str) -> IResult<&str, ElectricalProperties> {
29    let (remaining, properties) = many0(electrical_property).parse(input)?;
30    Ok((remaining, properties.into_iter().collect()))
31}
32
33/// Parse a single property entry for an electrical component.
34fn electrical_property(input: &str) -> IResult<&str, (String, f32)> {
35    let (remaining, (_prop_tag, prop_name, value)) =
36        ws_separated!((tag("PROP"), is_not(" "), float)).parse(input)?;
37    Ok((remaining, (prop_name.to_string(), value)))
38}
39
40/// Represent properties of an electrical component.
41type ElectricalProperties = HashMap<String, f32>;
42
43/// Represents an electrical component in the IDF format.
44/// http://www.aertia.com/docs/priware/IDF_V30_Spec.pdf#page=31
45#[derive(Debug, PartialEq, Clone, Default)]
46pub struct ElectricalComponent {
47    pub geometry_name: String,
48    pub part_number: String,
49    pub units: String,
50    pub height: f32,
51    pub outline: Vec<Point>,
52    pub properties: ElectricalProperties,
53}
54
55/// Represents a mechanical component in the IDF format.
56/// http://www.aertia.com/docs/priware/IDF_V30_Spec.pdf#page=34
57#[derive(Debug, PartialEq, Clone, Default, PartialOrd)]
58pub struct MechanicalComponent {
59    pub geometry_name: String,
60    pub part_number: String,
61    pub units: String,
62    pub height: f32,
63    pub outline: Vec<Point>,
64}
65
66/// Parses an electrical component from the input string.
67/// http://www.aertia.com/docs/priware/IDF_V30_Spec.pdf#page=31
68///
69/// # Example
70///
71/// ```
72/// use idf_parser::components::electrical_component;
73///
74/// let input = ".ELECTRICAL
75/// cs13_a pn-cap THOU 150.0
76/// 0 -55.0 55.0 0.0
77/// .END_ELECTRICAL";
78///
79/// let (remaining, component) = electrical_component(input).unwrap();
80/// assert_eq!(component.geometry_name, "cs13_a");
81/// ```
82pub fn electrical_component(input: &str) -> IResult<&str, ElectricalComponent> {
83    let (remaining, (geometry_name, part_number, units, height, outline, properties)) =
84        parse_section!(
85            "ELECTRICAL",
86            ws_separated!((
87                is_not(" "), // geometry name
88                alt((
89                    quote_string, // part number with quotes
90                    is_not(" "),  // part number without quotes
91                )),
92                is_not(" "),           // units
93                float,                 // height
94                many0(ws(point)),      // outline
95                electrical_properties  // electrical component properties
96            ))
97        )
98        .parse(input)?;
99
100    let electrical_component = ElectricalComponent {
101        geometry_name: geometry_name.to_string(),
102        part_number: part_number.to_string(),
103        units: units.to_string(),
104        height,
105        outline,
106        properties,
107    };
108
109    Ok((remaining, electrical_component))
110}
111
112/// Parses a mechanical component from the input string.
113/// http://www.aertia.com/docs/priware/IDF_V30_Spec.pdf#page=34
114/// # Example
115///
116/// ```
117/// use idf_parser::components::mechanical_component;
118///
119/// let input = ".MECHANICAL
120/// cs13_a pn-cap THOU 150.0
121/// 0 -55.0 55.0 0.0
122/// 0 -55.0 -55.0 0.0
123/// .END_MECHANICAL";
124///
125/// let (remaining, component) = mechanical_component(input).unwrap();
126/// assert_eq!(component.geometry_name, "cs13_a");
127/// ```
128pub fn mechanical_component(input: &str) -> IResult<&str, MechanicalComponent> {
129    let (remaining, (geometry_name, part_number, units, height, outline)) = parse_section!(
130        "MECHANICAL",
131        ws_separated!((
132            is_not(" "),      // geometry name
133            is_not(" "),      // part number
134            is_not(" "),      // units
135            float,            // height
136            many0(ws(point))  // outline
137        ))
138    )
139    .parse(input)?;
140
141    let mechanical_component = MechanicalComponent {
142        geometry_name: geometry_name.to_string(),
143        part_number: part_number.to_string(),
144        units: units.to_string(),
145        height,
146        outline,
147    };
148
149    Ok((remaining, mechanical_component))
150}
151
152#[cfg(test)]
153mod tests {
154    use super::*;
155    #[test]
156    fn test_electrical_component() {
157        let input = ".ELECTRICAL
158cs13_a pn-cap THOU 150.0
1590 -55.0 55.0 0.0
1600 -55.0 -55.0 0.0
1610 135.0 -55.0 0.0
1620 135.0 -80.0 0.0
1630 565.0 -80.0 0.0
1640 565.0 -55.0 0.0
1650 755.0 -55.0 0.0
1660 755.0 55.0 0.0
1670 565.0 55.0 0.0
1680 565.0 80.0 0.0
1690 135.0 80.0 0.0
1700 135.0 55.0 0.0
1710 -55.0 55.0 0.0
172PROP CAPACITANCE 100.0
173PROP TOLERANCE 5.0
174PROP RESISTANCE 122.0
175PROP POWER_OPR 2.5
176PROP POWER_MAX 9.12
177PROP THERM_COND 0.0
178PROP THETA_JB 0.2
179PROP THETA_JC 5.1
180.END_ELECTRICAL";
181        let (remaining, component) = electrical_component(input).unwrap();
182        assert_eq!(remaining, "");
183        assert_eq!(component.geometry_name, "cs13_a");
184        assert_eq!(component.part_number, "pn-cap");
185        assert_eq!(component.units, "THOU");
186        assert_eq!(component.height, 150.0);
187        assert_eq!(component.outline.len(), 13);
188        assert_eq!(component.properties["CAPACITANCE"], 100.0);
189        assert_eq!(component.properties["TOLERANCE"], 5.0);
190        assert_eq!(component.properties["RESISTANCE"], 122.0);
191        assert_eq!(component.properties["POWER_OPR"], 2.5);
192        assert_eq!(component.properties["POWER_MAX"], 9.12);
193        assert_eq!(component.properties["THERM_COND"], 0.0);
194        assert_eq!(component.properties["THETA_JB"], 0.2);
195        assert_eq!(component.properties["THETA_JC"], 5.1);
196    }
197
198    #[test]
199    fn test_electrical_component_2() {
200        let input = ".ELECTRICAL\r\nGLOB_FID_60R140  \"GLOB_FID_GLOB_FID_60R140_GLOB F\"  THOU         2.0\r\n0         0.0         0.0       0.000\r\n0        70.0         0.0     360.000\r\n.END_ELECTRICAL\r\n";
201
202        let (remaining, component) = electrical_component(input).unwrap();
203
204        let expected = ElectricalComponent {
205            geometry_name: "GLOB_FID_60R140".to_string(),
206            part_number: "GLOB_FID_GLOB_FID_60R140_GLOB F".to_string(),
207            units: "THOU".to_string(),
208            height: 2.0,
209            outline: vec![
210                Point {
211                    loop_label: 0,
212                    x: 0.0,
213                    y: 0.0,
214                    angle: 0.0,
215                },
216                Point {
217                    loop_label: 0,
218                    x: 70.0,
219                    y: 0.0,
220                    angle: 360.0,
221                },
222            ],
223            properties: HashMap::new(),
224        };
225        assert_eq!(remaining, "");
226        assert_eq!(component.geometry_name, expected.geometry_name);
227    }
228    #[test]
229    fn test_mechanical_component() {
230        let input = ".MECHANICAL
231cs13_a pn-cap THOU 150.0
2320 -55.0 55.0 0.0
2330 -55.0 -55.0 0.0
2340 135.0 -55.0 0.0
2350 135.0 -80.0 0.0
2360 565.0 -80.0 0.0
2370 565.0 -55.0 0.0
238.END_MECHANICAL";
239        let (remaining, component) = mechanical_component(input).unwrap();
240        assert_eq!(remaining, "");
241        assert_eq!(component.geometry_name, "cs13_a");
242        assert_eq!(component.part_number, "pn-cap");
243        assert_eq!(component.units, "THOU");
244        assert_eq!(component.height, 150.0);
245        assert_eq!(component.outline.len(), 6);
246        assert_eq!(
247            component.outline[0],
248            Point {
249                loop_label: 0,
250                x: -55.0,
251                y: 55.0,
252                angle: 0.0
253            }
254        );
255        assert_eq!(
256            component.outline[4],
257            Point {
258                loop_label: 0,
259                x: 565.0,
260                y: -80.0,
261                angle: 0.0
262            }
263        );
264    }
265}