gerber_types/
extended_codes.rs

1//! Extended code types.
2
3use std::io::Write;
4
5use crate::errors::GerberResult;
6use crate::traits::PartialGerberCode;
7use crate::MacroDecimal;
8use strum_macros;
9use strum_macros::{IntoStaticStr, VariantArray, VariantNames};
10
11// Unit
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, IntoStaticStr, VariantNames, VariantArray)]
14#[strum(serialize_all = "UPPERCASE")]
15pub enum Unit {
16    #[strum(serialize = "IN")]
17    Inches,
18    #[strum(serialize = "MM")]
19    Millimeters,
20}
21
22impl_partial_gerber_code_via_strum!(Unit);
23
24// ApertureDefinition
25
26#[derive(Debug, Clone, PartialEq)]
27pub struct ApertureDefinition {
28    pub code: i32,
29    pub aperture: Aperture,
30}
31
32impl ApertureDefinition {
33    pub fn new(code: i32, aperture: Aperture) -> Self {
34        ApertureDefinition { code, aperture }
35    }
36}
37
38impl<W: Write> PartialGerberCode<W> for ApertureDefinition {
39    fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
40        write!(writer, "{}", self.code)?;
41        self.aperture.serialize_partial(writer)?;
42        Ok(())
43    }
44}
45
46// Aperture
47
48#[derive(Debug, Clone, PartialEq)]
49pub enum Aperture {
50    Circle(Circle),
51    Rectangle(Rectangular),
52    Obround(Rectangular),
53    Polygon(Polygon),
54
55    /// gerber spec (2024.05) 4.3.1 "AD Command" - "Parameters are decimals."
56    ///
57    /// Note: this definition conflicts with:
58    /// a) the [`MacroBoolean`] which is used for the exposure parameter for macro primitives.
59    /// b) the [`MacroInteger`] which is used for the '# vertices' parameter for macro primitives.
60    ///
61    /// Conversion functions from MacroDecimal to [`MacroBoolean`] & [`MacroInteger`] are required.  
62    Macro(String, Option<Vec<MacroDecimal>>),
63}
64
65impl<W: Write> PartialGerberCode<W> for Aperture {
66    fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
67        match *self {
68            Aperture::Circle(ref circle) => {
69                write!(writer, "C,")?;
70                circle.serialize_partial(writer)?;
71            }
72            Aperture::Rectangle(ref rectangular) => {
73                write!(writer, "R,")?;
74                rectangular.serialize_partial(writer)?;
75            }
76            Aperture::Obround(ref rectangular) => {
77                write!(writer, "O,")?;
78                rectangular.serialize_partial(writer)?;
79            }
80            Aperture::Polygon(ref polygon) => {
81                write!(writer, "P,")?;
82                polygon.serialize_partial(writer)?;
83            }
84            Aperture::Macro(ref string, ref args) => {
85                write!(writer, "{}", string)?;
86                if let Some(ref args) = *args {
87                    write!(writer, ",")?;
88                    for (index, arg) in args.iter().enumerate() {
89                        if index > 0 {
90                            write!(writer, "X")?;
91                        }
92                        arg.serialize_partial(writer)?;
93                    }
94                }
95            }
96        };
97        Ok(())
98    }
99}
100
101// Circle
102
103#[derive(Debug, Clone, PartialEq)]
104pub struct Circle {
105    pub diameter: f64,
106    pub hole_diameter: Option<f64>,
107}
108
109impl Circle {
110    pub fn new(diameter: f64) -> Self {
111        Circle {
112            diameter,
113            hole_diameter: None,
114        }
115    }
116
117    pub fn with_hole(diameter: f64, hole_diameter: f64) -> Self {
118        Circle {
119            diameter,
120            hole_diameter: Some(hole_diameter),
121        }
122    }
123}
124
125impl<W: Write> PartialGerberCode<W> for Circle {
126    fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
127        match self.hole_diameter {
128            Some(hole_diameter) => {
129                write!(writer, "{}X{}", self.diameter, hole_diameter)?;
130            }
131            None => write!(writer, "{}", self.diameter)?,
132        };
133        Ok(())
134    }
135}
136
137// Rectangular
138
139#[derive(Debug, Clone, PartialEq)]
140pub struct Rectangular {
141    pub x: f64,
142    pub y: f64,
143    pub hole_diameter: Option<f64>,
144}
145
146impl Rectangular {
147    pub fn new(x: f64, y: f64) -> Self {
148        Rectangular {
149            x,
150            y,
151            hole_diameter: None,
152        }
153    }
154
155    pub fn with_hole(x: f64, y: f64, hole_diameter: f64) -> Self {
156        Rectangular {
157            x,
158            y,
159            hole_diameter: Some(hole_diameter),
160        }
161    }
162}
163
164impl<W: Write> PartialGerberCode<W> for Rectangular {
165    fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
166        match self.hole_diameter {
167            Some(hole_diameter) => write!(writer, "{}X{}X{}", self.x, self.y, hole_diameter)?,
168            None => write!(writer, "{}X{}", self.x, self.y)?,
169        };
170        Ok(())
171    }
172}
173
174// Polygon
175
176#[derive(Debug, Clone, PartialEq)]
177pub struct Polygon {
178    pub diameter: f64,
179    pub vertices: u8, // 3--12
180    pub rotation: Option<f64>,
181    pub hole_diameter: Option<f64>,
182}
183
184impl Polygon {
185    pub fn new(diameter: f64, vertices: u8) -> Self {
186        Polygon {
187            diameter,
188            vertices,
189            rotation: None,
190            hole_diameter: None,
191        }
192    }
193
194    pub fn with_rotation(mut self, angle: f64) -> Self {
195        self.rotation = Some(angle);
196        self
197    }
198
199    pub fn with_diameter(mut self, diameter: f64) -> Self {
200        self.diameter = diameter;
201        self
202    }
203}
204
205impl<W: Write> PartialGerberCode<W> for Polygon {
206    fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
207        match (self.rotation, self.hole_diameter) {
208            (Some(rot), Some(hd)) => {
209                write!(writer, "{}X{}X{}X{}", self.diameter, self.vertices, rot, hd)?
210            }
211            (Some(rot), None) => write!(writer, "{}X{}X{}", self.diameter, self.vertices, rot)?,
212            (None, Some(hd)) => write!(writer, "{}X{}X0X{}", self.diameter, self.vertices, hd)?,
213            (None, None) => write!(writer, "{}X{}", self.diameter, self.vertices)?,
214        };
215        Ok(())
216    }
217}
218
219// Polarity
220
221#[derive(Debug, Clone, Copy, PartialEq, Eq, IntoStaticStr, VariantNames, VariantArray)]
222#[strum(serialize_all = "UPPERCASE")]
223pub enum Polarity {
224    #[strum(serialize = "C")]
225    Clear,
226    #[strum(serialize = "D")]
227    Dark,
228}
229
230impl_partial_gerber_code_via_strum!(Polarity);
231
232// Mirroring
233
234#[derive(Debug, Clone, Copy, PartialEq, Eq, IntoStaticStr, VariantNames, VariantArray)]
235#[strum(serialize_all = "UPPERCASE")]
236pub enum Mirroring {
237    #[strum(serialize = "N")]
238    None,
239    X,
240    Y,
241    XY,
242}
243
244impl_partial_gerber_code_via_strum!(Mirroring);
245
246// Scaling
247
248#[derive(Debug, Clone, Copy, PartialEq)]
249pub struct Scaling {
250    pub scale: f64,
251}
252
253impl<W: Write> PartialGerberCode<W> for Scaling {
254    fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
255        write!(writer, "{}", self.scale)?;
256        Ok(())
257    }
258}
259
260// Rotation
261
262#[derive(Debug, Clone, Copy, PartialEq)]
263pub struct Rotation {
264    /// in degrees, counter-clockwise
265    pub rotation: f64,
266}
267
268impl<W: Write> PartialGerberCode<W> for Rotation {
269    fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
270        write!(writer, "{}", self.rotation)?;
271        Ok(())
272    }
273}
274
275// StepAndRepeat
276
277#[derive(Debug, Clone, PartialEq)]
278pub enum StepAndRepeat {
279    Open {
280        repeat_x: u32,
281        repeat_y: u32,
282        distance_x: f64,
283        distance_y: f64,
284    },
285    Close,
286}
287
288impl<W: Write> PartialGerberCode<W> for StepAndRepeat {
289    fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
290        match *self {
291            StepAndRepeat::Open {
292                repeat_x: rx,
293                repeat_y: ry,
294                distance_x: dx,
295                distance_y: dy,
296            } => write!(writer, "X{}Y{}I{}J{}", rx, ry, dx, dy)?,
297            StepAndRepeat::Close => {}
298        };
299        Ok(())
300    }
301}
302
303// ApertureBlock
304
305#[derive(Debug, Clone, PartialEq)]
306pub enum ApertureBlock {
307    Open { code: i32 },
308    Close,
309}
310
311impl<W: Write> PartialGerberCode<W> for ApertureBlock {
312    fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
313        match *self {
314            ApertureBlock::Open { code } => write!(writer, "{}", code)?,
315            ApertureBlock::Close => {}
316        };
317        Ok(())
318    }
319}
320
321#[cfg(test)]
322mod test {
323    use super::*;
324
325    #[test]
326    fn test_aperture_definition_new() {
327        let ad1 = ApertureDefinition::new(10, Aperture::Circle(Circle::new(3.0)));
328        let ad2 = ApertureDefinition {
329            code: 10,
330            aperture: Aperture::Circle(Circle::new(3.0)),
331        };
332        assert_eq!(ad1, ad2);
333    }
334
335    #[test]
336    fn test_rectangular_new() {
337        let r1 = Rectangular::new(2.0, 3.0);
338        let r2 = Rectangular {
339            x: 2.0,
340            y: 3.0,
341            hole_diameter: None,
342        };
343        assert_eq!(r1, r2);
344    }
345
346    #[test]
347    fn test_rectangular_with_hole() {
348        let r1 = Rectangular::with_hole(3.0, 2.0, 1.0);
349        let r2 = Rectangular {
350            x: 3.0,
351            y: 2.0,
352            hole_diameter: Some(1.0),
353        };
354        assert_eq!(r1, r2);
355    }
356
357    #[test]
358    fn test_circle_new() {
359        let c1 = Circle::new(3.0);
360        let c2 = Circle {
361            diameter: 3.0,
362            hole_diameter: None,
363        };
364        assert_eq!(c1, c2);
365    }
366
367    #[test]
368    fn test_circle_with_hole() {
369        let c1 = Circle::with_hole(3.0, 1.0);
370        let c2 = Circle {
371            diameter: 3.0,
372            hole_diameter: Some(1.0),
373        };
374        assert_eq!(c1, c2);
375    }
376
377    #[test]
378    fn test_polygon_new() {
379        let p1 = Polygon::new(3.0, 4).with_rotation(45.0);
380        let p2 = Polygon {
381            diameter: 3.0,
382            vertices: 4,
383            rotation: Some(45.0),
384            hole_diameter: None,
385        };
386        assert_eq!(p1, p2);
387    }
388
389    /// This test is to ensure that the `Unit` enum is hashable.
390    #[test]
391    fn unit_in_hashmap() {
392        let mut map = std::collections::HashMap::new();
393        map.insert(Unit::Inches, ());
394        map.insert(Unit::Millimeters, ());
395
396        assert_eq!(map.len(), 2);
397    }
398}
399
400// Image Mirroring
401
402/// Gerber spec 2024.05 8.1.7 "Mirror Image (MI)"
403#[derive(Debug, Clone, Copy, PartialEq, Eq, IntoStaticStr, VariantNames, VariantArray)]
404pub enum ImageMirroring {
405    #[strum(serialize = "")]
406    None,
407    #[strum(serialize = "A1")]
408    A,
409    #[strum(serialize = "B1")]
410    B,
411    #[strum(serialize = "A1B1")]
412    AB,
413}
414
415impl_partial_gerber_code_via_strum!(ImageMirroring);
416
417impl Default for ImageMirroring {
418    fn default() -> Self {
419        ImageMirroring::None
420    }
421}
422
423// Image Rotation
424
425/// Gerber spec 2024.05 8.1.5 "Image Rotation (IR)"
426#[derive(Debug, Clone, Copy, PartialEq, Eq, IntoStaticStr, VariantNames, VariantArray)]
427#[allow(non_camel_case_types)]
428pub enum ImageRotation {
429    #[strum(serialize = "0")]
430    None,
431    #[strum(serialize = "90")]
432    CCW_90,
433    #[strum(serialize = "180")]
434    CCW_180,
435    #[strum(serialize = "270")]
436    CCW_270,
437}
438
439impl_partial_gerber_code_via_strum!(ImageRotation);
440
441impl Default for ImageRotation {
442    fn default() -> Self {
443        ImageRotation::None
444    }
445}
446// Image Scaling
447
448/// Gerber spec 2024.05 8.1.9 "Scale Factor (SF)"
449/// By default, A=X, B=Y, but this changes depending on the axis select command (AS)
450#[derive(Debug, Clone, Copy, PartialEq)]
451pub struct ImageScaling {
452    /// scale factor for A axis
453    pub a: f64,
454    /// scale factor for B axis
455    pub b: f64,
456}
457
458impl<W: Write> PartialGerberCode<W> for ImageScaling {
459    fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
460        if self.a != 0.0 {
461            write!(writer, "A{}", self.a)?;
462        }
463        if self.b != 0.0 {
464            write!(writer, "B{}", self.b)?;
465        }
466        Ok(())
467    }
468}
469
470impl Default for ImageScaling {
471    fn default() -> Self {
472        ImageScaling { a: 1.0, b: 1.0 }
473    }
474}
475
476// Image Offset
477
478/// Gerber spec 2024.05 8.1.8 "Offset (OF)"
479/// By default, A=X, B=Y, but this changes depending on the axis select command (AS)
480#[derive(Debug, Clone, Copy, PartialEq, Default)]
481pub struct ImageOffset {
482    /// offset for A axis
483    pub a: f64,
484    /// offset for B axis
485    pub b: f64,
486}
487
488impl<W: Write> PartialGerberCode<W> for ImageOffset {
489    fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
490        if self.a != 0.0 {
491            write!(writer, "A{}", self.a)?;
492        }
493        if self.b != 0.0 {
494            write!(writer, "B{}", self.b)?;
495        }
496        Ok(())
497    }
498}
499
500// Axis Select
501
502#[derive(Debug, Clone, Copy, PartialEq, Eq, IntoStaticStr, VariantNames, VariantArray)]
503#[strum(serialize_all = "UPPERCASE")]
504pub enum AxisSelect {
505    AXBY,
506    AYBX,
507}
508
509impl_partial_gerber_code_via_strum!(AxisSelect);
510
511impl Default for AxisSelect {
512    fn default() -> Self {
513        AxisSelect::AXBY
514    }
515}
516
517// Image Polarity
518
519#[derive(Debug, Clone, Copy, PartialEq, Eq, IntoStaticStr, VariantNames, VariantArray)]
520pub enum ImagePolarity {
521    #[strum(serialize = "POS")]
522    Positive,
523    #[strum(serialize = "NEG")]
524    Negative,
525}
526
527impl_partial_gerber_code_via_strum!(ImagePolarity);
528
529impl Default for ImagePolarity {
530    fn default() -> Self {
531        ImagePolarity::Positive
532    }
533}
534
535/// Gerber spec 2024.05 8.1.9 "Scale Factor (SF)"
536/// By default, A=X, B=Y, but this changes depending on the axis select command (AS)
537#[derive(Debug, Clone, PartialEq)]
538pub struct ImageName {
539    pub name: String,
540}
541
542impl<W: Write> PartialGerberCode<W> for ImageName {
543    fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
544        write!(writer, "{}", self.name)?;
545        Ok(())
546    }
547}