Skip to main content

altium_format/records/pcb/
net.rs

1//! PCB net record type.
2//!
3//! Nets define electrical connectivity and routing properties
4//! for connected copper elements on the PCB.
5
6use crate::types::{Color, Coord, Layer, ParameterCollection};
7
8/// PCB net record.
9///
10/// A net represents an electrical connection between multiple points
11/// on the PCB. It stores routing properties like track widths for
12/// different layers and visual display settings.
13#[derive(Debug, Clone, Default)]
14pub struct PcbNet {
15    /// Net name.
16    pub name: String,
17    /// Layer (typically TOP for default).
18    pub layer: Layer,
19    /// Whether the net is locked.
20    pub locked: bool,
21    /// Whether the net is visible.
22    pub visible: bool,
23    /// Whether primitives are locked.
24    pub primitive_lock: bool,
25    /// Whether this is a user-routed net.
26    pub user_routed: bool,
27    /// Whether this is a keepout net.
28    pub keepout: bool,
29    /// Union index (for grouping).
30    pub union_index: i32,
31    /// Display color for the net.
32    pub color: Color,
33    /// Whether to use loop removal optimization.
34    pub loop_removal: bool,
35    /// Whether to override color for drawing.
36    pub override_color_for_draw: bool,
37    /// Layer-specific minimum routing widths (indexed by layer).
38    /// Format: TOPLAYER_MRWIDTH, MIDLAYER1_MRWIDTH, etc.
39    pub layer_widths: Vec<(String, Coord)>,
40    /// Unique ID.
41    pub unique_id: String,
42    /// All parameters for round-tripping.
43    pub params: ParameterCollection,
44}
45
46impl PcbNet {
47    /// Parse a net from parameters.
48    pub fn from_params(params: &ParameterCollection) -> Self {
49        let mut net = Self {
50            name: params
51                .get("NAME")
52                .map(|v| v.as_str().to_string())
53                .unwrap_or_default(),
54            layer: params
55                .get("LAYER")
56                .map(|v| v.as_layer())
57                .unwrap_or_default(),
58            locked: params
59                .get("LOCKED")
60                .map(|v| v.as_bool_or(false))
61                .unwrap_or(false),
62            visible: params
63                .get("VISIBLE")
64                .map(|v| v.as_bool_or(true))
65                .unwrap_or(true),
66            primitive_lock: params
67                .get("PRIMITIVELOCK")
68                .map(|v| v.as_bool_or(false))
69                .unwrap_or(false),
70            user_routed: params
71                .get("USERROUTED")
72                .map(|v| v.as_bool_or(true))
73                .unwrap_or(true),
74            keepout: params
75                .get("KEEPOUT")
76                .map(|v| v.as_bool_or(false))
77                .unwrap_or(false),
78            union_index: params
79                .get("UNIONINDEX")
80                .map(|v| v.as_int_or(0))
81                .unwrap_or(0),
82            color: params
83                .get("COLOR")
84                .and_then(|v| v.as_color().ok())
85                .unwrap_or_default(),
86            loop_removal: params
87                .get("LOOPREMOVAL")
88                .map(|v| v.as_bool_or(true))
89                .unwrap_or(true),
90            override_color_for_draw: params
91                .get("OVERRIDECOLORFORDRAW")
92                .map(|v| v.as_bool_or(false))
93                .unwrap_or(false),
94            layer_widths: Vec::new(),
95            unique_id: params
96                .get("UNIQUEID")
97                .map(|v| v.as_str().to_string())
98                .unwrap_or_default(),
99            params: params.clone(),
100        };
101
102        // Parse layer-specific widths
103        // TOPLAYER_MRWIDTH, MIDLAYER1_MRWIDTH, ..., MIDLAYER30_MRWIDTH, BOTTOMLAYER_MRWIDTH
104        for (key, value) in params.iter() {
105            if key.ends_with("_MRWIDTH") {
106                if let Ok(width) = value.as_coord() {
107                    let layer_name = key.trim_end_matches("_MRWIDTH").to_string();
108                    net.layer_widths.push((layer_name, width));
109                }
110            }
111        }
112
113        net
114    }
115
116    /// Export to parameters.
117    pub fn to_params(&self) -> ParameterCollection {
118        let mut params = self.params.clone();
119
120        params.add("NAME", &self.name);
121        params.add("LAYER", &self.layer.to_string());
122        params.add("LOCKED", if self.locked { "TRUE" } else { "FALSE" });
123        params.add("VISIBLE", if self.visible { "TRUE" } else { "FALSE" });
124        params.add(
125            "PRIMITIVELOCK",
126            if self.primitive_lock { "TRUE" } else { "FALSE" },
127        );
128        params.add(
129            "USERROUTED",
130            if self.user_routed { "TRUE" } else { "FALSE" },
131        );
132        params.add("KEEPOUT", if self.keepout { "TRUE" } else { "FALSE" });
133        params.add_int("UNIONINDEX", self.union_index);
134        params.add_color("COLOR", self.color);
135        params.add(
136            "LOOPREMOVAL",
137            if self.loop_removal { "TRUE" } else { "FALSE" },
138        );
139        params.add(
140            "OVERRIDECOLORFORDRAW",
141            if self.override_color_for_draw {
142                "TRUE"
143            } else {
144                "FALSE"
145            },
146        );
147
148        // Write layer widths
149        for (layer_name, width) in &self.layer_widths {
150            params.add_coord(&format!("{}_MRWIDTH", layer_name), *width);
151        }
152
153        if !self.unique_id.is_empty() {
154            params.add("UNIQUEID", &self.unique_id);
155        }
156
157        params
158    }
159
160    /// Get the minimum routing width for a specific layer.
161    pub fn get_layer_width(&self, layer_name: &str) -> Option<Coord> {
162        self.layer_widths
163            .iter()
164            .find(|(name, _)| name.eq_ignore_ascii_case(layer_name))
165            .map(|(_, width)| *width)
166    }
167
168    /// Get the top layer minimum routing width.
169    pub fn top_layer_width(&self) -> Option<Coord> {
170        self.get_layer_width("TOPLAYER")
171    }
172
173    /// Get the bottom layer minimum routing width.
174    pub fn bottom_layer_width(&self) -> Option<Coord> {
175        self.get_layer_width("BOTTOMLAYER")
176    }
177}