Skip to main content

altium_format/records/pcb/
board.rs

1//! PCB board settings and outline record type.
2//!
3//! The board record contains global PCB settings, grid configuration,
4//! and the board outline definition.
5
6use crate::types::{Coord, Layer, ParameterCollection};
7
8use super::polygon::{HatchStyle, PolygonType, PolygonVertex, PolygonVertexKind};
9
10/// Display unit mode.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
12#[repr(u8)]
13pub enum DisplayUnit {
14    /// Imperial units (mils).
15    #[default]
16    Imperial = 0,
17    /// Metric units (mm).
18    Metric = 1,
19}
20
21impl DisplayUnit {
22    /// Parse from integer value.
23    pub fn from_int(value: i32) -> Self {
24        match value {
25            1 => DisplayUnit::Metric,
26            _ => DisplayUnit::Imperial,
27        }
28    }
29
30    /// Convert to integer value.
31    pub fn to_int(self) -> i32 {
32        self as i32
33    }
34}
35
36/// Designator display mode.
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
38#[repr(u8)]
39pub enum DesignatorDisplayMode {
40    /// Show physical designators.
41    #[default]
42    Physical = 0,
43    /// Show logical designators.
44    Logical = 1,
45}
46
47impl DesignatorDisplayMode {
48    /// Parse from integer value.
49    pub fn from_int(value: i32) -> Self {
50        match value {
51            1 => DesignatorDisplayMode::Logical,
52            _ => DesignatorDisplayMode::Physical,
53        }
54    }
55
56    /// Convert to integer value.
57    pub fn to_int(self) -> i32 {
58        self as i32
59    }
60}
61
62/// PCB board settings and outline.
63///
64/// Contains global board settings including grid configuration,
65/// units, and the board outline polygon.
66#[derive(Debug, Clone, Default)]
67pub struct PcbBoard {
68    /// Layer (typically TOP).
69    pub layer: Layer,
70    /// Whether locked.
71    pub locked: bool,
72    /// Whether this is a polygon outline only.
73    pub polygon_outline: bool,
74    /// Source filename.
75    pub filename: String,
76    /// File format kind (e.g., "Protel_Advanced_PCB").
77    pub kind: String,
78    /// File version (e.g., "5,01").
79    pub version: String,
80    /// Creation/modification date.
81    pub date: String,
82    /// Creation/modification time.
83    pub time: String,
84    /// Origin X coordinate.
85    pub origin_x: Coord,
86    /// Origin Y coordinate.
87    pub origin_y: Coord,
88    /// Big visible grid size.
89    pub big_visible_grid_size: f64,
90    /// Visible grid size.
91    pub visible_grid_size: f64,
92    /// Electrical grid range.
93    pub electrical_grid_range: Coord,
94    /// Whether electrical grid is enabled.
95    pub electrical_grid_enabled: bool,
96    /// Snap grid size.
97    pub snap_grid_size: f64,
98    /// Snap grid size X.
99    pub snap_grid_size_x: f64,
100    /// Snap grid size Y.
101    pub snap_grid_size_y: f64,
102    /// Track placement grid size.
103    pub track_grid_size: f64,
104    /// Via placement grid size.
105    pub via_grid_size: f64,
106    /// Component placement grid size.
107    pub component_grid_size: f64,
108    /// Component grid size X.
109    pub component_grid_size_x: f64,
110    /// Component grid size Y.
111    pub component_grid_size_y: f64,
112    /// Whether to show dot grid.
113    pub dot_grid: bool,
114    /// Display unit mode (Imperial/Metric).
115    pub display_unit: DisplayUnit,
116    /// Designator display mode.
117    pub designator_display_mode: DesignatorDisplayMode,
118    /// Whether primitives are locked.
119    pub primitive_lock: bool,
120    /// Default polygon type.
121    pub polygon_type: PolygonType,
122    /// Default pour over setting.
123    pub pour_over: bool,
124    /// Default remove dead copper setting.
125    pub remove_dead: bool,
126    /// Default grid size for polygons.
127    pub grid_size: Coord,
128    /// Default track width.
129    pub track_width: Coord,
130    /// Default hatch style.
131    pub hatch_style: HatchStyle,
132    /// Whether to use octagons.
133    pub use_octagons: bool,
134    /// Minimum primitive length.
135    pub min_prim_length: Coord,
136    /// Board outline vertices.
137    pub outline: Vec<PolygonVertex>,
138    /// All parameters for round-tripping.
139    pub params: ParameterCollection,
140}
141
142impl PcbBoard {
143    /// Parse board settings from parameters.
144    pub fn from_params(params: &ParameterCollection) -> Self {
145        let mut board = Self {
146            layer: params
147                .get("LAYER")
148                .map(|v| v.as_layer())
149                .unwrap_or_default(),
150            locked: params
151                .get("LOCKED")
152                .map(|v| v.as_bool_or(false))
153                .unwrap_or(false),
154            polygon_outline: params
155                .get("POLYGONOUTLINE")
156                .map(|v| v.as_bool_or(false))
157                .unwrap_or(false),
158            filename: params
159                .get("FILENAME")
160                .map(|v| v.as_str().to_string())
161                .unwrap_or_default(),
162            kind: params
163                .get("KIND")
164                .map(|v| v.as_str().to_string())
165                .unwrap_or_default(),
166            version: params
167                .get("VERSION")
168                .map(|v| v.as_str().to_string())
169                .unwrap_or_default(),
170            date: params
171                .get("DATE")
172                .map(|v| v.as_str().to_string())
173                .unwrap_or_default(),
174            time: params
175                .get("TIME")
176                .map(|v| v.as_str().to_string())
177                .unwrap_or_default(),
178            origin_x: params
179                .get("ORIGINX")
180                .and_then(|v| v.as_coord().ok())
181                .unwrap_or_default(),
182            origin_y: params
183                .get("ORIGINY")
184                .and_then(|v| v.as_coord().ok())
185                .unwrap_or_default(),
186            big_visible_grid_size: params
187                .get("BIGVISIBLEGRIDSIZE")
188                .map(|v| v.as_double_or(0.0))
189                .unwrap_or(0.0),
190            visible_grid_size: params
191                .get("VISIBLEGRIDSIZE")
192                .map(|v| v.as_double_or(0.0))
193                .unwrap_or(0.0),
194            electrical_grid_range: params
195                .get("ELECTRICALGRIDRANGE")
196                .and_then(|v| v.as_coord().ok())
197                .unwrap_or_default(),
198            electrical_grid_enabled: params
199                .get("ELECTRICALGRIDENABLED")
200                .map(|v| v.as_bool_or(true))
201                .unwrap_or(true),
202            snap_grid_size: params
203                .get("SNAPGRIDSIZE")
204                .map(|v| v.as_double_or(0.0))
205                .unwrap_or(0.0),
206            snap_grid_size_x: params
207                .get("SNAPGRIDSIZEX")
208                .map(|v| v.as_double_or(0.0))
209                .unwrap_or(0.0),
210            snap_grid_size_y: params
211                .get("SNAPGRIDSIZEY")
212                .map(|v| v.as_double_or(0.0))
213                .unwrap_or(0.0),
214            track_grid_size: params
215                .get("TRACKGRIDSIZE")
216                .map(|v| v.as_double_or(0.0))
217                .unwrap_or(0.0),
218            via_grid_size: params
219                .get("VIAGRIDSIZE")
220                .map(|v| v.as_double_or(0.0))
221                .unwrap_or(0.0),
222            component_grid_size: params
223                .get("COMPONENTGRIDSIZE")
224                .map(|v| v.as_double_or(0.0))
225                .unwrap_or(0.0),
226            component_grid_size_x: params
227                .get("COMPONENTGRIDSIZEX")
228                .map(|v| v.as_double_or(0.0))
229                .unwrap_or(0.0),
230            component_grid_size_y: params
231                .get("COMPONENTGRIDSIZEY")
232                .map(|v| v.as_double_or(0.0))
233                .unwrap_or(0.0),
234            dot_grid: params
235                .get("DOTGRID")
236                .map(|v| v.as_bool_or(true))
237                .unwrap_or(true),
238            display_unit: params
239                .get("DISPLAYUNIT")
240                .map(|v| DisplayUnit::from_int(v.as_int_or(0)))
241                .unwrap_or_default(),
242            designator_display_mode: params
243                .get("DESIGNATORDISPLAYMODE")
244                .map(|v| DesignatorDisplayMode::from_int(v.as_int_or(0)))
245                .unwrap_or_default(),
246            primitive_lock: params
247                .get("PRIMITIVELOCK")
248                .map(|v| v.as_bool_or(false))
249                .unwrap_or(false),
250            polygon_type: params
251                .get("POLYGONTYPE")
252                .map(|v| PolygonType::parse(v.as_str()))
253                .unwrap_or_default(),
254            pour_over: params
255                .get("POUROVER")
256                .map(|v| v.as_bool_or(false))
257                .unwrap_or(false),
258            remove_dead: params
259                .get("REMOVEDEAD")
260                .map(|v| v.as_bool_or(false))
261                .unwrap_or(false),
262            grid_size: params
263                .get("GRIDSIZE")
264                .and_then(|v| v.as_coord().ok())
265                .unwrap_or_default(),
266            track_width: params
267                .get("TRACKWIDTH")
268                .and_then(|v| v.as_coord().ok())
269                .unwrap_or_default(),
270            hatch_style: params
271                .get("HATCHSTYLE")
272                .map(|v| HatchStyle::parse(v.as_str()))
273                .unwrap_or_default(),
274            use_octagons: params
275                .get("USEOCTAGONS")
276                .map(|v| v.as_bool_or(false))
277                .unwrap_or(false),
278            min_prim_length: params
279                .get("MINPRIMLENGTH")
280                .and_then(|v| v.as_coord().ok())
281                .unwrap_or_default(),
282            outline: Vec::new(),
283            params: params.clone(),
284        };
285
286        // Parse board outline vertices (same format as polygon)
287        let mut idx = 0;
288        loop {
289            let vx_key = format!("VX{}", idx);
290            let vy_key = format!("VY{}", idx);
291
292            if !params.contains(&vx_key) {
293                break;
294            }
295
296            let vertex = PolygonVertex {
297                kind: params
298                    .get(&format!("KIND{}", idx))
299                    .map(|v| PolygonVertexKind::from_int(v.as_int_or(0)))
300                    .unwrap_or_default(),
301                x: params
302                    .get(&vx_key)
303                    .and_then(|v| v.as_coord().ok())
304                    .unwrap_or_default(),
305                y: params
306                    .get(&vy_key)
307                    .and_then(|v| v.as_coord().ok())
308                    .unwrap_or_default(),
309                center_x: params
310                    .get(&format!("CX{}", idx))
311                    .and_then(|v| v.as_coord().ok())
312                    .unwrap_or_default(),
313                center_y: params
314                    .get(&format!("CY{}", idx))
315                    .and_then(|v| v.as_coord().ok())
316                    .unwrap_or_default(),
317                start_angle: params
318                    .get(&format!("SA{}", idx))
319                    .map(|v| v.as_double_or(0.0))
320                    .unwrap_or(0.0),
321                end_angle: params
322                    .get(&format!("EA{}", idx))
323                    .map(|v| v.as_double_or(0.0))
324                    .unwrap_or(0.0),
325                radius: params
326                    .get(&format!("R{}", idx))
327                    .and_then(|v| v.as_coord().ok())
328                    .unwrap_or_default(),
329            };
330
331            board.outline.push(vertex);
332            idx += 1;
333        }
334
335        board
336    }
337
338    /// Export to parameters.
339    pub fn to_params(&self) -> ParameterCollection {
340        let mut params = self.params.clone();
341
342        params.add("LAYER", &self.layer.to_string());
343        params.add("LOCKED", if self.locked { "TRUE" } else { "FALSE" });
344        params.add(
345            "POLYGONOUTLINE",
346            if self.polygon_outline {
347                "TRUE"
348            } else {
349                "FALSE"
350            },
351        );
352        params.add("FILENAME", &self.filename);
353        params.add("KIND", &self.kind);
354        params.add("VERSION", &self.version);
355        params.add("DATE", &self.date);
356        params.add("TIME", &self.time);
357        params.add_coord("ORIGINX", self.origin_x);
358        params.add_coord("ORIGINY", self.origin_y);
359        params.add_double("BIGVISIBLEGRIDSIZE", self.big_visible_grid_size, 3);
360        params.add_double("VISIBLEGRIDSIZE", self.visible_grid_size, 3);
361        params.add_coord("ELECTRICALGRIDRANGE", self.electrical_grid_range);
362        params.add(
363            "ELECTRICALGRIDENABLED",
364            if self.electrical_grid_enabled {
365                "TRUE"
366            } else {
367                "FALSE"
368            },
369        );
370        params.add_double("SNAPGRIDSIZE", self.snap_grid_size, 6);
371        params.add_double("SNAPGRIDSIZEX", self.snap_grid_size_x, 6);
372        params.add_double("SNAPGRIDSIZEY", self.snap_grid_size_y, 6);
373        params.add_double("TRACKGRIDSIZE", self.track_grid_size, 6);
374        params.add_double("VIAGRIDSIZE", self.via_grid_size, 6);
375        params.add_double("COMPONENTGRIDSIZE", self.component_grid_size, 6);
376        params.add_double("COMPONENTGRIDSIZEX", self.component_grid_size_x, 6);
377        params.add_double("COMPONENTGRIDSIZEY", self.component_grid_size_y, 6);
378        params.add("DOTGRID", if self.dot_grid { "TRUE" } else { "FALSE" });
379        params.add_int("DISPLAYUNIT", self.display_unit.to_int());
380        params.add_int(
381            "DESIGNATORDISPLAYMODE",
382            self.designator_display_mode.to_int(),
383        );
384        params.add(
385            "PRIMITIVELOCK",
386            if self.primitive_lock { "TRUE" } else { "FALSE" },
387        );
388        params.add("POLYGONTYPE", self.polygon_type.as_str());
389        params.add("POUROVER", if self.pour_over { "TRUE" } else { "FALSE" });
390        params.add(
391            "REMOVEDEAD",
392            if self.remove_dead { "TRUE" } else { "FALSE" },
393        );
394        params.add_coord("GRIDSIZE", self.grid_size);
395        params.add_coord("TRACKWIDTH", self.track_width);
396        params.add("HATCHSTYLE", self.hatch_style.as_str());
397        params.add(
398            "USEOCTAGONS",
399            if self.use_octagons { "TRUE" } else { "FALSE" },
400        );
401        params.add_coord("MINPRIMLENGTH", self.min_prim_length);
402
403        // Write outline vertices
404        for (idx, vertex) in self.outline.iter().enumerate() {
405            params.add_int(&format!("KIND{}", idx), vertex.kind.to_int());
406            params.add_coord(&format!("VX{}", idx), vertex.x);
407            params.add_coord(&format!("VY{}", idx), vertex.y);
408            params.add_coord(&format!("CX{}", idx), vertex.center_x);
409            params.add_coord(&format!("CY{}", idx), vertex.center_y);
410            params.add_double(&format!("SA{}", idx), vertex.start_angle, 14);
411            params.add_double(&format!("EA{}", idx), vertex.end_angle, 14);
412            params.add_coord(&format!("R{}", idx), vertex.radius);
413        }
414
415        params
416    }
417
418    /// Get the number of outline vertices.
419    pub fn outline_vertex_count(&self) -> usize {
420        self.outline.len()
421    }
422
423    /// Check if using metric units.
424    pub fn is_metric(&self) -> bool {
425        matches!(self.display_unit, DisplayUnit::Metric)
426    }
427}