altium_format/records/pcb/
polygon.rs1use crate::types::{Coord, Layer, ParameterCollection};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
10pub enum PolygonType {
11 #[default]
13 Polygon,
14 SplitPlane,
16 Cutout,
18}
19
20impl PolygonType {
21 pub fn parse(s: &str) -> Self {
23 match s.to_uppercase().as_str() {
24 "POLYGON" => PolygonType::Polygon,
25 "SPLITPLANE" => PolygonType::SplitPlane,
26 "CUTOUT" => PolygonType::Cutout,
27 _ => PolygonType::Polygon,
28 }
29 }
30
31 pub fn as_str(&self) -> &'static str {
33 match self {
34 PolygonType::Polygon => "Polygon",
35 PolygonType::SplitPlane => "SplitPlane",
36 PolygonType::Cutout => "Cutout",
37 }
38 }
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
43pub enum HatchStyle {
44 None,
46 Hatch45,
48 Hatch90,
50 HatchHorizontal,
52 HatchVertical,
54 #[default]
56 Solid,
57}
58
59impl HatchStyle {
60 pub fn parse(s: &str) -> Self {
62 match s.to_uppercase().as_str() {
63 "NONE" => HatchStyle::None,
64 "45DEGREE" | "HATCH45" => HatchStyle::Hatch45,
65 "90DEGREE" | "HATCH90" => HatchStyle::Hatch90,
66 "HORIZONTAL" => HatchStyle::HatchHorizontal,
67 "VERTICAL" => HatchStyle::HatchVertical,
68 "SOLID" => HatchStyle::Solid,
69 _ => HatchStyle::Solid,
70 }
71 }
72
73 pub fn as_str(&self) -> &'static str {
75 match self {
76 HatchStyle::None => "None",
77 HatchStyle::Hatch45 => "45Degree",
78 HatchStyle::Hatch90 => "90Degree",
79 HatchStyle::HatchHorizontal => "Horizontal",
80 HatchStyle::HatchVertical => "Vertical",
81 HatchStyle::Solid => "Solid",
82 }
83 }
84}
85
86#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
88#[repr(u8)]
89pub enum PolygonVertexKind {
90 #[default]
92 Line = 0,
93 Arc = 1,
95}
96
97impl PolygonVertexKind {
98 pub fn from_int(value: i32) -> Self {
100 match value {
101 1 => PolygonVertexKind::Arc,
102 _ => PolygonVertexKind::Line,
103 }
104 }
105
106 pub fn to_int(self) -> i32 {
108 self as i32
109 }
110}
111
112#[derive(Debug, Clone, Default)]
114pub struct PolygonVertex {
115 pub kind: PolygonVertexKind,
117 pub x: Coord,
119 pub y: Coord,
121 pub center_x: Coord,
123 pub center_y: Coord,
125 pub start_angle: f64,
127 pub end_angle: f64,
129 pub radius: Coord,
131}
132
133#[derive(Debug, Clone, Default)]
138pub struct PcbPolygon {
139 pub layer: Layer,
141 pub locked: bool,
143 pub polygon_outline: bool,
145 pub user_routed: bool,
147 pub keepout: bool,
149 pub union_index: i32,
151 pub primitive_lock: bool,
153 pub polygon_type: PolygonType,
155 pub pour_over: bool,
157 pub remove_dead: bool,
159 pub grid_size: Coord,
161 pub track_width: Coord,
163 pub hatch_style: HatchStyle,
165 pub use_octagons: bool,
167 pub min_prim_length: Coord,
169 pub net_name: String,
171 pub unique_id: String,
173 pub vertices: Vec<PolygonVertex>,
175 pub params: ParameterCollection,
177}
178
179impl PcbPolygon {
180 pub fn from_params(params: &ParameterCollection) -> Self {
182 let mut polygon = Self {
183 layer: params
184 .get("LAYER")
185 .map(|v| v.as_layer())
186 .unwrap_or_default(),
187 locked: params
188 .get("LOCKED")
189 .map(|v| v.as_bool_or(false))
190 .unwrap_or(false),
191 polygon_outline: params
192 .get("POLYGONOUTLINE")
193 .map(|v| v.as_bool_or(false))
194 .unwrap_or(false),
195 user_routed: params
196 .get("USERROUTED")
197 .map(|v| v.as_bool_or(true))
198 .unwrap_or(true),
199 keepout: params
200 .get("KEEPOUT")
201 .map(|v| v.as_bool_or(false))
202 .unwrap_or(false),
203 union_index: params
204 .get("UNIONINDEX")
205 .map(|v| v.as_int_or(0))
206 .unwrap_or(0),
207 primitive_lock: params
208 .get("PRIMITIVELOCK")
209 .map(|v| v.as_bool_or(false))
210 .unwrap_or(false),
211 polygon_type: params
212 .get("POLYGONTYPE")
213 .map(|v| PolygonType::parse(v.as_str()))
214 .unwrap_or_default(),
215 pour_over: params
216 .get("POUROVER")
217 .map(|v| v.as_bool_or(false))
218 .unwrap_or(false),
219 remove_dead: params
220 .get("REMOVEDEAD")
221 .map(|v| v.as_bool_or(false))
222 .unwrap_or(false),
223 grid_size: params
224 .get("GRIDSIZE")
225 .and_then(|v| v.as_coord().ok())
226 .unwrap_or_default(),
227 track_width: params
228 .get("TRACKWIDTH")
229 .and_then(|v| v.as_coord().ok())
230 .unwrap_or_default(),
231 hatch_style: params
232 .get("HATCHSTYLE")
233 .map(|v| HatchStyle::parse(v.as_str()))
234 .unwrap_or_default(),
235 use_octagons: params
236 .get("USEOCTAGONS")
237 .map(|v| v.as_bool_or(false))
238 .unwrap_or(false),
239 min_prim_length: params
240 .get("MINPRIMLENGTH")
241 .and_then(|v| v.as_coord().ok())
242 .unwrap_or_default(),
243 net_name: params
244 .get("NET")
245 .or_else(|| params.get("NETNAME"))
246 .map(|v| v.as_str().to_string())
247 .unwrap_or_default(),
248 unique_id: params
249 .get("UNIQUEID")
250 .map(|v| v.as_str().to_string())
251 .unwrap_or_default(),
252 vertices: Vec::new(),
253 params: params.clone(),
254 };
255
256 let mut idx = 0;
258 loop {
259 let kind_key = format!("KIND{}", idx);
260 let vx_key = format!("VX{}", idx);
261 let vy_key = format!("VY{}", idx);
262
263 if !params.contains(&vx_key) {
264 break;
265 }
266
267 let vertex = PolygonVertex {
268 kind: params
269 .get(&kind_key)
270 .map(|v| PolygonVertexKind::from_int(v.as_int_or(0)))
271 .unwrap_or_default(),
272 x: params
273 .get(&vx_key)
274 .and_then(|v| v.as_coord().ok())
275 .unwrap_or_default(),
276 y: params
277 .get(&vy_key)
278 .and_then(|v| v.as_coord().ok())
279 .unwrap_or_default(),
280 center_x: params
281 .get(&format!("CX{}", idx))
282 .and_then(|v| v.as_coord().ok())
283 .unwrap_or_default(),
284 center_y: params
285 .get(&format!("CY{}", idx))
286 .and_then(|v| v.as_coord().ok())
287 .unwrap_or_default(),
288 start_angle: params
289 .get(&format!("SA{}", idx))
290 .map(|v| v.as_double_or(0.0))
291 .unwrap_or(0.0),
292 end_angle: params
293 .get(&format!("EA{}", idx))
294 .map(|v| v.as_double_or(0.0))
295 .unwrap_or(0.0),
296 radius: params
297 .get(&format!("R{}", idx))
298 .and_then(|v| v.as_coord().ok())
299 .unwrap_or_default(),
300 };
301
302 polygon.vertices.push(vertex);
303 idx += 1;
304 }
305
306 polygon
307 }
308
309 pub fn to_params(&self) -> ParameterCollection {
311 let mut params = self.params.clone();
312
313 params.add("LAYER", &self.layer.to_string());
314 params.add("LOCKED", if self.locked { "TRUE" } else { "FALSE" });
315 params.add(
316 "POLYGONOUTLINE",
317 if self.polygon_outline {
318 "TRUE"
319 } else {
320 "FALSE"
321 },
322 );
323 params.add(
324 "USERROUTED",
325 if self.user_routed { "TRUE" } else { "FALSE" },
326 );
327 params.add("KEEPOUT", if self.keepout { "TRUE" } else { "FALSE" });
328 params.add_int("UNIONINDEX", self.union_index);
329 params.add(
330 "PRIMITIVELOCK",
331 if self.primitive_lock { "TRUE" } else { "FALSE" },
332 );
333 params.add("POLYGONTYPE", self.polygon_type.as_str());
334 params.add("POUROVER", if self.pour_over { "TRUE" } else { "FALSE" });
335 params.add(
336 "REMOVEDEAD",
337 if self.remove_dead { "TRUE" } else { "FALSE" },
338 );
339 params.add_coord("GRIDSIZE", self.grid_size);
340 params.add_coord("TRACKWIDTH", self.track_width);
341 params.add("HATCHSTYLE", self.hatch_style.as_str());
342 params.add(
343 "USEOCTAGONS",
344 if self.use_octagons { "TRUE" } else { "FALSE" },
345 );
346 params.add_coord("MINPRIMLENGTH", self.min_prim_length);
347
348 if !self.net_name.is_empty() {
349 params.add("NET", &self.net_name);
350 }
351 if !self.unique_id.is_empty() {
352 params.add("UNIQUEID", &self.unique_id);
353 }
354
355 for (idx, vertex) in self.vertices.iter().enumerate() {
357 params.add_int(&format!("KIND{}", idx), vertex.kind.to_int());
358 params.add_coord(&format!("VX{}", idx), vertex.x);
359 params.add_coord(&format!("VY{}", idx), vertex.y);
360 params.add_coord(&format!("CX{}", idx), vertex.center_x);
361 params.add_coord(&format!("CY{}", idx), vertex.center_y);
362 params.add_double(&format!("SA{}", idx), vertex.start_angle, 14);
363 params.add_double(&format!("EA{}", idx), vertex.end_angle, 14);
364 params.add_coord(&format!("R{}", idx), vertex.radius);
365 }
366
367 params
368 }
369
370 pub fn vertex_count(&self) -> usize {
372 self.vertices.len()
373 }
374}