Skip to main content

altium_format/records/pcb/
mod.rs

1//! PCB record types for Altium PCB library files.
2//!
3//! PCB records use binary format with object IDs.
4
5mod arc;
6mod board;
7mod class;
8mod component;
9mod component_body;
10mod connection;
11mod differential_pair;
12mod fill;
13mod fromto;
14mod net;
15mod options;
16mod outline;
17mod pad;
18mod polygon;
19mod primitive;
20mod region;
21mod rule;
22mod text;
23mod track;
24mod via;
25
26pub use arc::*;
27pub use board::*;
28pub use class::*;
29pub use component::*;
30pub use component_body::*;
31pub use connection::*;
32pub use differential_pair::*;
33pub use fill::*;
34pub use fromto::*;
35pub use net::*;
36pub use options::*;
37pub use pad::*;
38pub use polygon::*;
39pub use primitive::*;
40pub use region::*;
41pub use rule::*;
42pub use text::*;
43pub use track::*;
44pub use via::*;
45
46pub(crate) use outline::PcbOutline;
47
48// DumpTree implementations
49use crate::dump::{
50    DumpTree, TreeBuilder, fmt_angle, fmt_bool, fmt_coord_point, fmt_coord_val, fmt_layer,
51};
52
53impl DumpTree for PcbArc {
54    fn dump(&self, tree: &mut TreeBuilder) {
55        tree.add_leaf(
56            "Arc",
57            &[
58                ("layer", fmt_layer(&self.common.layer)),
59                ("center", fmt_coord_point(&self.location)),
60                ("radius", fmt_coord_val(&self.radius)),
61                (
62                    "angles",
63                    format!(
64                        "{} → {}",
65                        fmt_angle(self.start_angle),
66                        fmt_angle(self.end_angle)
67                    ),
68                ),
69                ("width", fmt_coord_val(&self.width)),
70            ],
71        );
72    }
73}
74
75impl DumpTree for PcbTrack {
76    fn dump(&self, tree: &mut TreeBuilder) {
77        tree.add_leaf(
78            "Track",
79            &[
80                ("layer", fmt_layer(&self.common.layer)),
81                ("start", fmt_coord_point(&self.start)),
82                ("end", fmt_coord_point(&self.end)),
83                ("width", fmt_coord_val(&self.width)),
84            ],
85        );
86    }
87}
88
89impl DumpTree for PcbPad {
90    fn dump(&self, tree: &mut TreeBuilder) {
91        let mut props = vec![
92            ("designator", self.designator.clone()),
93            ("layer", fmt_layer(&self.common.layer)),
94            ("location", fmt_coord_point(&self.location)),
95            ("size (top)", fmt_coord_point(&self.size_top())),
96            ("shape (top)", format!("{:?}", self.shape_top())),
97            ("hole", fmt_coord_val(&self.hole_size)),
98        ];
99        if self.rotation != 0.0 {
100            props.push(("rotation", fmt_angle(self.rotation)));
101        }
102        props.push(("plated", fmt_bool(self.is_plated)));
103        tree.add_leaf("Pad", &props);
104    }
105}
106
107impl DumpTree for PcbVia {
108    fn dump(&self, tree: &mut TreeBuilder) {
109        tree.add_leaf(
110            "Via",
111            &[
112                ("location", fmt_coord_point(&self.location)),
113                (
114                    "layers",
115                    format!(
116                        "{} → {}",
117                        fmt_layer(&self.from_layer),
118                        fmt_layer(&self.to_layer)
119                    ),
120                ),
121                ("diameter", fmt_coord_val(&self.diameter())),
122                ("hole", fmt_coord_val(&self.hole_size)),
123            ],
124        );
125    }
126}
127
128impl DumpTree for PcbText {
129    fn dump(&self, tree: &mut TreeBuilder) {
130        let mut props = vec![
131            ("text", format!("\"{}\"", self.text)),
132            ("layer", fmt_layer(&self.base.common.layer)),
133            ("position", fmt_coord_point(&self.base.corner1)),
134            ("height", fmt_coord_val(&self.height())),
135        ];
136        if self.base.rotation != 0.0 {
137            props.push(("rotation", fmt_angle(self.base.rotation)));
138        }
139        if self.mirrored {
140            props.push(("mirrored", "yes".to_string()));
141        }
142        if !self.font_name.is_empty() {
143            props.push(("font", self.font_name.clone()));
144        }
145        tree.add_leaf("Text", &props);
146    }
147}
148
149impl DumpTree for PcbFill {
150    fn dump(&self, tree: &mut TreeBuilder) {
151        let mut props = vec![
152            ("layer", fmt_layer(&self.base.common.layer)),
153            ("corner1", fmt_coord_point(&self.base.corner1)),
154            ("corner2", fmt_coord_point(&self.base.corner2)),
155        ];
156        if self.base.rotation != 0.0 {
157            props.push(("rotation", fmt_angle(self.base.rotation)));
158        }
159        tree.add_leaf("Fill", &props);
160    }
161}
162
163impl DumpTree for PcbRegion {
164    fn dump(&self, tree: &mut TreeBuilder) {
165        tree.add_leaf(
166            "Region",
167            &[
168                ("layer", fmt_layer(&self.common.layer)),
169                ("vertices", format!("{} points", self.outline.len())),
170            ],
171        );
172    }
173}
174
175impl DumpTree for PcbComponentBody {
176    fn dump(&self, tree: &mut TreeBuilder) {
177        let mut props = vec![("layer", fmt_layer(&self.common.layer))];
178        if !self.unique_id.is_empty() {
179            props.push(("unique_id", self.unique_id.clone()));
180        }
181        if !self.model_id.is_empty() {
182            props.push(("3D model", self.model_id.clone()));
183        }
184        props.push(("height", fmt_coord_val(&self.overall_height)));
185        props.push(("vertices", format!("{} points", self.outline.len())));
186        tree.add_leaf("ComponentBody", &props);
187    }
188}
189
190impl DumpTree for PcbRecord {
191    fn dump(&self, tree: &mut TreeBuilder) {
192        match self {
193            PcbRecord::Arc(r) => r.dump(tree),
194            PcbRecord::Pad(r) => r.dump(tree),
195            PcbRecord::Via(r) => r.dump(tree),
196            PcbRecord::Track(r) => r.dump(tree),
197            PcbRecord::Text(r) => r.dump(tree),
198            PcbRecord::Fill(r) => r.dump(tree),
199            PcbRecord::Region(r) => r.dump(tree),
200            PcbRecord::ComponentBody(r) => r.dump(tree),
201            PcbRecord::Polygon(r) => r.dump(tree),
202            PcbRecord::Unknown {
203                object_id,
204                raw_data,
205            } => {
206                tree.add_leaf(
207                    "Unknown",
208                    &[
209                        ("object_id", format!("{:?}", object_id)),
210                        ("size", format!("{} bytes", raw_data.len())),
211                    ],
212                );
213            }
214        }
215    }
216}
217
218impl DumpTree for PcbComponent {
219    fn dump(&self, tree: &mut TreeBuilder) {
220        tree.begin_node(&format!("Footprint: {}", self.pattern));
221        tree.push(true);
222
223        // Metadata section
224        tree.push(!self.primitives.is_empty());
225        let mut meta_props = vec![];
226        if !self.description.is_empty() {
227            meta_props.push(("description", self.description.clone()));
228        }
229        if self.height.to_raw() != 0 {
230            meta_props.push(("height", fmt_coord_val(&self.height)));
231        }
232        meta_props.push(("pads", format!("{}", self.pad_count())));
233        meta_props.push(("primitives", format!("{}", self.primitive_count())));
234        tree.add_leaf("Info", &meta_props);
235        tree.pop();
236
237        // Primitives section
238        if !self.primitives.is_empty() {
239            tree.push(false);
240            tree.begin_node(&format!("Primitives ({})", self.primitives.len()));
241            for (i, prim) in self.primitives.iter().enumerate() {
242                tree.push(i < self.primitives.len() - 1);
243                prim.dump(tree);
244                tree.pop();
245            }
246            tree.pop();
247        }
248
249        tree.pop();
250    }
251}
252
253impl DumpTree for PcbRule {
254    fn dump(&self, tree: &mut TreeBuilder) {
255        let mut props = vec![
256            ("kind", format!("{}", self.kind)),
257            ("enabled", fmt_bool(self.enabled)),
258            ("priority", format!("{}", self.priority)),
259        ];
260
261        if !self.scope1_expression.is_empty() && self.scope1_expression != "All" {
262            props.push(("scope1", self.scope1_expression.clone()));
263        }
264        if !self.scope2_expression.is_empty() && self.scope2_expression != "All" {
265            props.push(("scope2", self.scope2_expression.clone()));
266        }
267        if !self.comment.is_empty() {
268            props.push(("comment", self.comment.clone()));
269        }
270
271        tree.add_leaf(&format!("Rule: {}", self.name), &props);
272    }
273}
274
275impl DumpTree for PcbPolygon {
276    fn dump(&self, tree: &mut TreeBuilder) {
277        let mut props = vec![
278            ("layer", fmt_layer(&self.layer)),
279            ("type", self.polygon_type.as_str().to_string()),
280            ("vertices", format!("{} points", self.vertices.len())),
281        ];
282        if !self.net_name.is_empty() {
283            props.push(("net", self.net_name.clone()));
284        }
285        props.push(("hatch", self.hatch_style.as_str().to_string()));
286        tree.add_leaf("Polygon", &props);
287    }
288}
289
290impl DumpTree for PcbNet {
291    fn dump(&self, tree: &mut TreeBuilder) {
292        let mut props = vec![("name", self.name.clone())];
293        if !self.layer_widths.is_empty() {
294            props.push((
295                "layer widths",
296                format!("{} layers", self.layer_widths.len()),
297            ));
298        }
299        tree.add_leaf("Net", &props);
300    }
301}
302
303impl DumpTree for PcbDifferentialPair {
304    fn dump(&self, tree: &mut TreeBuilder) {
305        tree.add_leaf(
306            "DifferentialPair",
307            &[
308                ("name", self.name.clone()),
309                ("positive", self.positive_net_name.clone()),
310                ("negative", self.negative_net_name.clone()),
311            ],
312        );
313    }
314}
315
316impl DumpTree for PcbFromTo {
317    fn dump(&self, tree: &mut TreeBuilder) {
318        tree.add_leaf(
319            "FromTo",
320            &[
321                ("from", self.from.clone()),
322                ("to", self.to.clone()),
323                ("net", self.net.clone()),
324            ],
325        );
326    }
327}
328
329impl DumpTree for PcbConnection {
330    fn dump(&self, tree: &mut TreeBuilder) {
331        tree.add_leaf(
332            "Connection",
333            &[
334                ("net_index", format!("{}", self.net_index)),
335                (
336                    "from",
337                    format!(
338                        "({}, {})",
339                        fmt_coord_val(&self.from_x),
340                        fmt_coord_val(&self.from_y)
341                    ),
342                ),
343                (
344                    "to",
345                    format!(
346                        "({}, {})",
347                        fmt_coord_val(&self.to_x),
348                        fmt_coord_val(&self.to_y)
349                    ),
350                ),
351            ],
352        );
353    }
354}
355
356impl DumpTree for PcbBoard {
357    fn dump(&self, tree: &mut TreeBuilder) {
358        let mut props = vec![
359            ("version", self.version.clone()),
360            (
361                "display_unit",
362                if self.is_metric() { "mm" } else { "mil" }.to_string(),
363            ),
364            ("outline", format!("{} vertices", self.outline.len())),
365        ];
366        if !self.date.is_empty() {
367            props.push(("date", self.date.clone()));
368        }
369        tree.add_leaf("Board", &props);
370    }
371}