gerber_types/
macros.rs

1//! Aperture Macros.
2
3use std::convert::From;
4use std::io::Write;
5
6use crate::errors::{GerberError, GerberResult};
7use crate::traits::PartialGerberCode;
8
9#[derive(Debug, Clone, PartialEq)]
10pub struct ApertureMacro {
11    pub name: String,
12    pub content: Vec<MacroContent>,
13}
14
15impl ApertureMacro {
16    pub fn new<S: Into<String>>(name: S) -> Self {
17        ApertureMacro {
18            name: name.into(),
19            content: Vec::new(),
20        }
21    }
22
23    pub fn add_content<C>(mut self, c: C) -> Self
24    where
25        C: Into<MacroContent>,
26    {
27        self.content.push(c.into());
28        self
29    }
30
31    pub fn add_content_mut<C>(&mut self, c: C)
32    where
33        C: Into<MacroContent>,
34    {
35        self.content.push(c.into());
36    }
37}
38
39impl<W: Write> PartialGerberCode<W> for ApertureMacro {
40    fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
41        if self.content.is_empty() {
42            return Err(GerberError::MissingDataError(
43                "There must be at least 1 content element in an aperture macro".into(),
44            ));
45        }
46        writeln!(writer, "AM{}*", self.name)?;
47        let mut first = true;
48        for content in &self.content {
49            if first {
50                first = false;
51            } else {
52                writeln!(writer)?;
53            }
54            content.serialize_partial(writer)?;
55        }
56        Ok(())
57    }
58}
59
60#[derive(Debug, Clone, PartialEq)]
61/// A macro decimal can either be an f64 or a variable placeholder.
62pub enum MacroDecimal {
63    /// A decimal value.
64    Value(f64),
65    /// A variable placeholder.
66    Variable(u32),
67}
68
69impl MacroDecimal {
70    fn is_negative(&self) -> bool {
71        match *self {
72            MacroDecimal::Value(v) => v < 0.0,
73            MacroDecimal::Variable(_) => false,
74        }
75    }
76}
77
78impl From<f32> for MacroDecimal {
79    fn from(val: f32) -> Self {
80        MacroDecimal::Value(val as f64)
81    }
82}
83
84impl From<f64> for MacroDecimal {
85    fn from(val: f64) -> Self {
86        MacroDecimal::Value(val)
87    }
88}
89
90impl<W: Write> PartialGerberCode<W> for MacroDecimal {
91    fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
92        match *self {
93            MacroDecimal::Value(ref v) => write!(writer, "{}", v)?,
94            MacroDecimal::Variable(ref v) => write!(writer, "${}", v)?,
95        };
96        Ok(())
97    }
98}
99
100#[derive(Debug, Clone, PartialEq)]
101pub enum MacroContent {
102    // Primitives
103    Circle(CirclePrimitive),
104    VectorLine(VectorLinePrimitive),
105    CenterLine(CenterLinePrimitive),
106    Outline(OutlinePrimitive),
107    Polygon(PolygonPrimitive),
108    Moire(MoirePrimitive),
109    Thermal(ThermalPrimitive),
110
111    // Variables
112    VariableDefinition(VariableDefinition),
113
114    // Comment
115    Comment(String),
116}
117
118impl<W: Write> PartialGerberCode<W> for MacroContent {
119    fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
120        match *self {
121            MacroContent::Circle(ref c) => c.serialize_partial(writer)?,
122            MacroContent::VectorLine(ref vl) => vl.serialize_partial(writer)?,
123            MacroContent::CenterLine(ref cl) => cl.serialize_partial(writer)?,
124            MacroContent::Outline(ref o) => o.serialize_partial(writer)?,
125            MacroContent::Polygon(ref p) => p.serialize_partial(writer)?,
126            MacroContent::Moire(ref m) => m.serialize_partial(writer)?,
127            MacroContent::Thermal(ref t) => t.serialize_partial(writer)?,
128            MacroContent::Comment(ref s) => write!(writer, "0 {}*", &s)?,
129            MacroContent::VariableDefinition(ref v) => v.serialize_partial(writer)?,
130        };
131        Ok(())
132    }
133}
134
135macro_rules! impl_into {
136    ($target:ty, $from:ty, $choice:expr) => {
137        impl From<$from> for $target {
138            fn from(val: $from) -> $target {
139                $choice(val)
140            }
141        }
142    };
143}
144
145impl_into!(MacroContent, CirclePrimitive, MacroContent::Circle);
146impl_into!(MacroContent, VectorLinePrimitive, MacroContent::VectorLine);
147impl_into!(MacroContent, CenterLinePrimitive, MacroContent::CenterLine);
148impl_into!(MacroContent, OutlinePrimitive, MacroContent::Outline);
149impl_into!(MacroContent, PolygonPrimitive, MacroContent::Polygon);
150impl_into!(MacroContent, MoirePrimitive, MacroContent::Moire);
151impl_into!(MacroContent, ThermalPrimitive, MacroContent::Thermal);
152impl_into!(
153    MacroContent,
154    VariableDefinition,
155    MacroContent::VariableDefinition
156);
157
158impl<T: Into<String>> From<T> for MacroContent {
159    fn from(val: T) -> Self {
160        MacroContent::Comment(val.into())
161    }
162}
163
164#[derive(Debug, Clone, PartialEq)]
165pub struct CirclePrimitive {
166    /// Exposure off/on
167    pub exposure: bool,
168
169    /// Diameter, a decimal >= 0
170    pub diameter: MacroDecimal,
171
172    /// X and Y coordinates of center position, decimals
173    pub center: (MacroDecimal, MacroDecimal),
174
175    /// Rotation angle.
176    ///
177    /// The rotation angle is specified by a decimal, in degrees. The primitive
178    /// is rotated around the origin of the macro definition, i.e. the (0, 0)
179    /// point of macro coordinates.
180    ///
181    /// The rotation modifier is optional. The default is no rotation. (We
182    /// recommend always to set the angle explicitly.
183    pub angle: Option<MacroDecimal>,
184}
185
186impl CirclePrimitive {
187    pub fn new(diameter: MacroDecimal) -> Self {
188        CirclePrimitive {
189            exposure: true,
190            diameter,
191            center: (MacroDecimal::Value(0.0), MacroDecimal::Value(0.0)),
192            angle: None,
193        }
194    }
195
196    pub fn centered_at(mut self, center: (MacroDecimal, MacroDecimal)) -> Self {
197        self.center = center;
198        self
199    }
200
201    pub fn exposure_on(mut self, exposure: bool) -> Self {
202        self.exposure = exposure;
203        self
204    }
205
206    pub fn with_angle(mut self, angle: MacroDecimal) -> Self {
207        self.angle = Some(angle);
208        self
209    }
210}
211
212impl<W: Write> PartialGerberCode<W> for CirclePrimitive {
213    fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
214        write!(writer, "1,")?;
215        self.exposure.serialize_partial(writer)?;
216        write!(writer, ",")?;
217        self.diameter.serialize_partial(writer)?;
218        write!(writer, ",")?;
219        self.center.0.serialize_partial(writer)?;
220        write!(writer, ",")?;
221        self.center.1.serialize_partial(writer)?;
222        if let Some(ref a) = self.angle {
223            write!(writer, ",")?;
224            a.serialize_partial(writer)?;
225        }
226        write!(writer, "*")?;
227        Ok(())
228    }
229}
230
231#[derive(Debug, Clone, PartialEq)]
232pub struct VectorLinePrimitive {
233    /// Exposure off/on
234    pub exposure: bool,
235
236    /// Line width, a decimal >= 0
237    pub width: MacroDecimal,
238
239    /// X and Y coordinates of start point, decimals
240    pub start: (MacroDecimal, MacroDecimal),
241
242    /// X and Y coordinates of end point, decimals
243    pub end: (MacroDecimal, MacroDecimal),
244
245    /// Rotation angle of the vector line primitive
246    ///
247    /// The rotation angle is specified by a decimal, in degrees. The primitive
248    /// is rotated around the origin of the macro definition, i.e. the (0, 0)
249    /// point of macro coordinates.
250    pub angle: MacroDecimal,
251}
252
253impl VectorLinePrimitive {
254    pub fn new(start: (MacroDecimal, MacroDecimal), end: (MacroDecimal, MacroDecimal)) -> Self {
255        VectorLinePrimitive {
256            exposure: true,
257            width: MacroDecimal::Value(0.0),
258            start,
259            end,
260            angle: MacroDecimal::Value(0.0),
261        }
262    }
263
264    pub fn exposure_on(mut self, exposure: bool) -> Self {
265        self.exposure = exposure;
266        self
267    }
268
269    pub fn with_width(mut self, width: MacroDecimal) -> Self {
270        self.width = width;
271        self
272    }
273
274    pub fn with_angle(mut self, angle: MacroDecimal) -> Self {
275        self.angle = angle;
276        self
277    }
278}
279
280impl<W: Write> PartialGerberCode<W> for VectorLinePrimitive {
281    fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
282        write!(writer, "20,")?;
283        self.exposure.serialize_partial(writer)?;
284        write!(writer, ",")?;
285        self.width.serialize_partial(writer)?;
286        write!(writer, ",")?;
287        self.start.0.serialize_partial(writer)?;
288        write!(writer, ",")?;
289        self.start.1.serialize_partial(writer)?;
290        write!(writer, ",")?;
291        self.end.0.serialize_partial(writer)?;
292        write!(writer, ",")?;
293        self.end.1.serialize_partial(writer)?;
294        write!(writer, ",")?;
295        self.angle.serialize_partial(writer)?;
296        write!(writer, "*")?;
297        Ok(())
298    }
299}
300
301#[derive(Debug, Clone, PartialEq)]
302pub struct CenterLinePrimitive {
303    /// Exposure off/on (0/1)
304    pub exposure: bool,
305
306    /// Rectangle dimensions (width/height)
307    pub dimensions: (MacroDecimal, MacroDecimal),
308
309    /// X and Y coordinates of center point, decimals
310    pub center: (MacroDecimal, MacroDecimal),
311
312    /// Rotation angle
313    ///
314    /// The rotation angle is specified by a decimal, in degrees. The primitive
315    /// is rotated around the origin of the macro definition, i.e. (0, 0) point
316    /// of macro coordinates.
317    pub angle: MacroDecimal,
318}
319
320impl CenterLinePrimitive {
321    pub fn new(dimensions: (MacroDecimal, MacroDecimal)) -> Self {
322        CenterLinePrimitive {
323            exposure: true,
324            dimensions,
325            center: (MacroDecimal::Value(0.0), MacroDecimal::Value(0.0)),
326            angle: MacroDecimal::Value(0.0),
327        }
328    }
329
330    pub fn exposure_on(mut self, exposure: bool) -> Self {
331        self.exposure = exposure;
332        self
333    }
334
335    pub fn centered_at(mut self, center: (MacroDecimal, MacroDecimal)) -> Self {
336        self.center = center;
337        self
338    }
339
340    pub fn with_angle(mut self, angle: MacroDecimal) -> Self {
341        self.angle = angle;
342        self
343    }
344}
345
346impl<W: Write> PartialGerberCode<W> for CenterLinePrimitive {
347    fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
348        write!(writer, "21,")?;
349        self.exposure.serialize_partial(writer)?;
350        write!(writer, ",")?;
351        self.dimensions.0.serialize_partial(writer)?;
352        write!(writer, ",")?;
353        self.dimensions.1.serialize_partial(writer)?;
354        write!(writer, ",")?;
355        self.center.0.serialize_partial(writer)?;
356        write!(writer, ",")?;
357        self.center.1.serialize_partial(writer)?;
358        write!(writer, ",")?;
359        self.angle.serialize_partial(writer)?;
360        write!(writer, "*")?;
361        Ok(())
362    }
363}
364
365#[derive(Debug, Clone, PartialEq)]
366pub struct OutlinePrimitive {
367    /// Exposure off/on (0/1)
368    pub exposure: bool,
369
370    /// Vector of coordinate pairs.
371    ///
372    /// The last coordinate pair must be equal to the first coordinate pair!
373    pub points: Vec<(MacroDecimal, MacroDecimal)>,
374
375    /// Rotation angle of the outline primitive
376    ///
377    /// The rotation angle is specified by a decimal, in degrees. The primitive
378    /// is rotated around the origin of the macro definition, i.e. the (0, 0)
379    /// point of macro coordinates.
380    pub angle: MacroDecimal,
381}
382
383impl OutlinePrimitive {
384    pub fn new() -> Self {
385        OutlinePrimitive {
386            exposure: true,
387            points: Vec::new(),
388            angle: MacroDecimal::Value(0.0),
389        }
390    }
391
392    pub fn from_points(points: Vec<(MacroDecimal, MacroDecimal)>) -> Self {
393        let mut outline_prim = Self::new();
394        outline_prim.points = points;
395        outline_prim
396    }
397
398    pub fn add_point(mut self, point: (MacroDecimal, MacroDecimal)) -> Self {
399        self.points.push(point);
400        self
401    }
402
403    pub fn with_angle(mut self, angle: MacroDecimal) -> Self {
404        self.angle = angle;
405        self
406    }
407}
408
409impl<W: Write> PartialGerberCode<W> for OutlinePrimitive {
410    fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
411        // Points invariants
412        if self.points.len() < 2 {
413            return Err(GerberError::MissingDataError(
414                "There must be at least 1 subsequent point in an outline".into(),
415            ));
416        }
417        if self.points.len() > 5001 {
418            return Err(GerberError::RangeError(
419                "The maximum number of subsequent points in an outline is 5000".into(),
420            ));
421        }
422        if self.points[0] != self.points[self.points.len() - 1] {
423            return Err(GerberError::RangeError(
424                "The last point must be equal to the first point".into(),
425            ));
426        }
427
428        write!(writer, "4,")?;
429        self.exposure.serialize_partial(writer)?;
430        writeln!(writer, ",{},", self.points.len() - 1)?;
431
432        for &(ref x, ref y) in &self.points {
433            x.serialize_partial(writer)?;
434            write!(writer, ",")?;
435            y.serialize_partial(writer)?;
436            writeln!(writer, ",")?;
437        }
438        self.angle.serialize_partial(writer)?;
439        write!(writer, "*")?;
440        Ok(())
441    }
442}
443
444#[derive(Debug, Clone, PartialEq)]
445/// A polygon primitive is a regular polygon defined by the number of vertices,
446/// the center point and the diameter of the circumscribed circle.
447pub struct PolygonPrimitive {
448    /// Exposure off/on (0/1)
449    pub exposure: bool,
450
451    /// Number of vertices n, 3 <= n <= 12
452    pub vertices: u8,
453
454    /// X and Y coordinates of center point, decimals
455    pub center: (MacroDecimal, MacroDecimal),
456
457    /// Diameter of the circumscribed circle, a decimal >= 0
458    pub diameter: MacroDecimal,
459
460    /// Rotation angle of the polygon primitive
461    ///
462    /// The rotation angle is specified by a decimal, in degrees. The primitive
463    /// is rotated around the origin of the macro definition, i.e. the (0, 0)
464    /// point of macro coordinates. The first vertex is on the positive X-axis
465    /// through the center point when the rotation angle is zero.
466    ///
467    /// Note: Rotation is only allowed if the primitive center point coincides
468    /// with the origin of the macro definition.
469    pub angle: MacroDecimal,
470}
471
472impl PolygonPrimitive {
473    pub fn new(vertices: u8) -> Self {
474        PolygonPrimitive {
475            exposure: true,
476            vertices,
477            center: (MacroDecimal::Value(0.0), MacroDecimal::Value(0.0)),
478            diameter: MacroDecimal::Value(0.0),
479            angle: MacroDecimal::Value(0.0),
480        }
481    }
482
483    pub fn exposure_on(mut self, exposure: bool) -> Self {
484        self.exposure = exposure;
485        self
486    }
487
488    pub fn centered_at(mut self, center: (MacroDecimal, MacroDecimal)) -> Self {
489        self.center = center;
490        self
491    }
492
493    pub fn with_diameter(mut self, diameter: MacroDecimal) -> Self {
494        self.diameter = diameter;
495        self
496    }
497
498    pub fn with_angle(mut self, angle: MacroDecimal) -> Self {
499        self.angle = angle;
500        self
501    }
502}
503
504impl<W: Write> PartialGerberCode<W> for PolygonPrimitive {
505    fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
506        // Vertice count invariants
507        if self.vertices < 3 {
508            return Err(GerberError::MissingDataError(
509                "There must be at least 3 vertices in a polygon".into(),
510            ));
511        }
512        if self.vertices > 12 {
513            return Err(GerberError::RangeError(
514                "The maximum number of vertices in a polygon is 12".into(),
515            ));
516        }
517        if self.diameter.is_negative() {
518            return Err(GerberError::RangeError(
519                "The diameter must not be negative".into(),
520            ));
521        }
522        write!(writer, "5,")?;
523        self.exposure.serialize_partial(writer)?;
524        write!(writer, ",{},", self.vertices)?;
525        self.center.0.serialize_partial(writer)?;
526        write!(writer, ",")?;
527        self.center.1.serialize_partial(writer)?;
528        write!(writer, ",")?;
529        self.diameter.serialize_partial(writer)?;
530        write!(writer, ",")?;
531        self.angle.serialize_partial(writer)?;
532        write!(writer, "*")?;
533        Ok(())
534    }
535}
536
537/// The moiré primitive is a cross hair centered on concentric rings (annuli).
538/// Exposure is always on.
539#[derive(Debug, Clone, PartialEq)]
540pub struct MoirePrimitive {
541    /// X and Y coordinates of center point, decimals
542    pub center: (MacroDecimal, MacroDecimal),
543
544    /// Outer diameter of outer concentric ring, a decimal >= 0
545    pub diameter: MacroDecimal,
546
547    /// Ring thickness, a decimal >= 0
548    pub ring_thickness: MacroDecimal,
549
550    /// Gap between rings, a decimal >= 0
551    pub gap: MacroDecimal,
552
553    /// Maximum number of rings
554    pub max_rings: u32,
555
556    /// Cross hair thickness, a decimal >= 0
557    pub cross_hair_thickness: MacroDecimal,
558
559    /// Cross hair length, a decimal >= 0
560    pub cross_hair_length: MacroDecimal,
561
562    /// Rotation angle of the moiré primitive
563    ///
564    /// The rotation angle is specified by a decimal, in degrees. The primitive
565    /// is rotated around the origin of the macro definition, i.e. the (0, 0)
566    /// point of macro coordinates.
567    ///
568    /// Note: Rotation is only allowed if the primitive center point coincides
569    /// with the origin of the macro definition.
570    pub angle: MacroDecimal,
571}
572
573impl MoirePrimitive {
574    pub fn new() -> Self {
575        MoirePrimitive {
576            center: (MacroDecimal::Value(0.0), MacroDecimal::Value(0.0)),
577            diameter: MacroDecimal::Value(0.0),
578            ring_thickness: MacroDecimal::Value(0.0),
579            gap: MacroDecimal::Value(0.0),
580            max_rings: 1,
581            cross_hair_thickness: MacroDecimal::Value(0.0),
582            cross_hair_length: MacroDecimal::Value(0.0),
583            angle: MacroDecimal::Value(0.0),
584        }
585    }
586
587    pub fn centered_at(mut self, center: (MacroDecimal, MacroDecimal)) -> Self {
588        self.center = center;
589        self
590    }
591
592    pub fn with_diameter(mut self, diameter: MacroDecimal) -> Self {
593        self.diameter = diameter;
594        self
595    }
596
597    pub fn with_rings_max(mut self, max_rings: u32) -> Self {
598        self.max_rings = max_rings;
599        self
600    }
601
602    pub fn with_ring_thickness(mut self, thickness: MacroDecimal) -> Self {
603        self.ring_thickness = thickness;
604        self
605    }
606
607    pub fn with_gap(mut self, gap: MacroDecimal) -> Self {
608        self.gap = gap;
609        self
610    }
611
612    pub fn with_cross_thickness(mut self, thickness: MacroDecimal) -> Self {
613        self.cross_hair_thickness = thickness;
614        self
615    }
616
617    pub fn with_cross_length(mut self, length: MacroDecimal) -> Self {
618        self.cross_hair_length = length;
619        self
620    }
621
622    pub fn with_angle(mut self, angle: MacroDecimal) -> Self {
623        self.angle = angle;
624        self
625    }
626}
627
628impl<W: Write> PartialGerberCode<W> for MoirePrimitive {
629    fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
630        // Decimal invariants
631        if self.diameter.is_negative() {
632            return Err(GerberError::RangeError(
633                "Outer diameter of a moiré may not be negative".into(),
634            ));
635        }
636        if self.ring_thickness.is_negative() {
637            return Err(GerberError::RangeError(
638                "Ring thickness of a moiré may not be negative".into(),
639            ));
640        }
641        if self.gap.is_negative() {
642            return Err(GerberError::RangeError(
643                "Gap of a moiré may not be negative".into(),
644            ));
645        }
646        if self.cross_hair_thickness.is_negative() {
647            return Err(GerberError::RangeError(
648                "Cross hair thickness of a moiré may not be negative".into(),
649            ));
650        }
651        if self.cross_hair_length.is_negative() {
652            return Err(GerberError::RangeError(
653                "Cross hair length of a moiré may not be negative".into(),
654            ));
655        }
656        write!(writer, "6,")?;
657        self.center.0.serialize_partial(writer)?;
658        write!(writer, ",")?;
659        self.center.1.serialize_partial(writer)?;
660        write!(writer, ",")?;
661        self.diameter.serialize_partial(writer)?;
662        write!(writer, ",")?;
663        self.ring_thickness.serialize_partial(writer)?;
664        write!(writer, ",")?;
665        self.gap.serialize_partial(writer)?;
666        write!(writer, ",{},", self.max_rings)?;
667        self.cross_hair_thickness.serialize_partial(writer)?;
668        write!(writer, ",")?;
669        self.cross_hair_length.serialize_partial(writer)?;
670        write!(writer, ",")?;
671        self.angle.serialize_partial(writer)?;
672        write!(writer, "*")?;
673        Ok(())
674    }
675}
676
677/// The thermal primitive is a ring (annulus) interrupted by four gaps.
678/// Exposure is always on.
679#[derive(Debug, Clone, PartialEq)]
680pub struct ThermalPrimitive {
681    /// X and Y coordinates of center point, decimals
682    pub center: (MacroDecimal, MacroDecimal),
683
684    /// Outer diameter, a decimal > inner diameter
685    pub outer_diameter: MacroDecimal,
686
687    /// Inner diameter, a decimal >= 0
688    pub inner_diameter: MacroDecimal,
689
690    /// Gap thickness, a decimal < (outer diameter) / sqrt(2)
691    pub gap: MacroDecimal,
692
693    /// Rotation angle of the thermal primitive
694    ///
695    /// The rotation angle is specified by a decimal, in degrees. The primitive
696    /// is rotated around the origin of the macro definition, i.e. the (0, 0)
697    /// point of macro coordinates. The gaps are on the X and Y axes through
698    /// the center when the rotation angle is zero
699    ///
700    /// Note: Rotation is only allowed if the primitive center point coincides
701    /// with the origin of the macro definition.
702    pub angle: MacroDecimal,
703}
704
705impl ThermalPrimitive {
706    pub fn new(inner: MacroDecimal, outer: MacroDecimal, gap: MacroDecimal) -> Self {
707        ThermalPrimitive {
708            center: (MacroDecimal::Value(0.0), MacroDecimal::Value(0.0)),
709            outer_diameter: outer,
710            inner_diameter: inner,
711            gap,
712            angle: MacroDecimal::Value(0.0),
713        }
714    }
715
716    pub fn centered_at(mut self, center: (MacroDecimal, MacroDecimal)) -> Self {
717        self.center = center;
718        self
719    }
720
721    pub fn with_angle(mut self, angle: MacroDecimal) -> Self {
722        self.angle = angle;
723        self
724    }
725}
726
727impl<W: Write> PartialGerberCode<W> for ThermalPrimitive {
728    fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
729        // Decimal invariants
730        if self.inner_diameter.is_negative() {
731            return Err(GerberError::RangeError(
732                "Inner diameter of a thermal may not be negative".into(),
733            ));
734        }
735        write!(writer, "7,")?;
736        self.center.0.serialize_partial(writer)?;
737        write!(writer, ",")?;
738        self.center.1.serialize_partial(writer)?;
739        write!(writer, ",")?;
740        self.outer_diameter.serialize_partial(writer)?;
741        write!(writer, ",")?;
742        self.inner_diameter.serialize_partial(writer)?;
743        write!(writer, ",")?;
744        self.gap.serialize_partial(writer)?;
745        write!(writer, ",")?;
746        self.angle.serialize_partial(writer)?;
747        write!(writer, "*")?;
748        Ok(())
749    }
750}
751
752#[derive(Debug, Clone, PartialEq, Eq)]
753pub struct VariableDefinition {
754    number: u32,
755    expression: String,
756}
757
758impl VariableDefinition {
759    pub fn new(number: u32, expr: &str) -> Self {
760        VariableDefinition {
761            number,
762            expression: expr.into(),
763        }
764    }
765}
766
767impl<W: Write> PartialGerberCode<W> for VariableDefinition {
768    fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
769        write!(writer, "${}={}*", self.number, self.expression)?;
770        Ok(())
771    }
772}
773
774#[cfg(test)]
775mod test {
776    use std::io::BufWriter;
777
778    use crate::traits::PartialGerberCode;
779
780    use super::MacroDecimal::{Value, Variable};
781    use super::*;
782
783    macro_rules! assert_partial_code {
784        ($obj:expr, $expected:expr) => {
785            let mut buf = BufWriter::new(Vec::new());
786            $obj.serialize_partial(&mut buf)
787                .expect("Could not generate Gerber code");
788            let bytes = buf.into_inner().unwrap();
789            let code = String::from_utf8(bytes).unwrap();
790            assert_eq!(&code, $expected);
791        };
792    }
793
794    #[test]
795    fn test_circle_primitive_codegen() {
796        let with_angle = CirclePrimitive {
797            exposure: true,
798            diameter: Value(1.5),
799            center: (Value(0.), Value(0.)),
800            angle: Some(Value(0.)),
801        };
802        assert_partial_code!(with_angle, "1,1,1.5,0,0,0*");
803        let no_angle = CirclePrimitive {
804            exposure: false,
805            diameter: Value(99.9),
806            center: (Value(1.1), Value(2.2)),
807            angle: None,
808        };
809        assert_partial_code!(no_angle, "1,0,99.9,1.1,2.2*");
810    }
811
812    #[test]
813    fn test_vector_line_primitive_codegen() {
814        let line = VectorLinePrimitive {
815            exposure: true,
816            width: Value(0.9),
817            start: (Value(0.), Value(0.45)),
818            end: (Value(12.), Value(0.45)),
819            angle: Value(0.),
820        };
821        assert_partial_code!(line, "20,1,0.9,0,0.45,12,0.45,0*");
822    }
823
824    #[test]
825    fn test_center_line_primitive_codegen() {
826        let line = CenterLinePrimitive {
827            exposure: true,
828            dimensions: (Value(6.8), Value(1.2)),
829            center: (Value(3.4), Value(0.6)),
830            angle: Value(30.0),
831        };
832        assert_partial_code!(line, "21,1,6.8,1.2,3.4,0.6,30*");
833    }
834
835    #[test]
836    fn test_outline_primitive_codegen() {
837        let line = OutlinePrimitive {
838            exposure: true,
839            points: vec![
840                (Value(0.1), Value(0.1)),
841                (Value(0.5), Value(0.1)),
842                (Value(0.5), Value(0.5)),
843                (Value(0.1), Value(0.5)),
844                (Value(0.1), Value(0.1)),
845            ],
846            angle: Value(0.0),
847        };
848        assert_partial_code!(
849            line,
850            "4,1,4,\n0.1,0.1,\n0.5,0.1,\n0.5,0.5,\n0.1,0.5,\n0.1,0.1,\n0*"
851        );
852    }
853
854    #[test]
855    fn test_polygon_primitive_codegen() {
856        let line = PolygonPrimitive {
857            exposure: true,
858            vertices: 8,
859            center: (Value(1.5), Value(2.0)),
860            diameter: Value(8.0),
861            angle: Value(0.0),
862        };
863        assert_partial_code!(line, "5,1,8,1.5,2,8,0*");
864    }
865
866    #[test]
867    fn test_moire_primitive_codegen() {
868        let line = MoirePrimitive {
869            center: (Value(0.0), Value(0.0)),
870            diameter: Value(5.0),
871            ring_thickness: Value(0.5),
872            gap: Value(0.5),
873            max_rings: 2,
874            cross_hair_thickness: Value(0.1),
875            cross_hair_length: Value(6.0),
876            angle: Value(0.0),
877        };
878        assert_partial_code!(line, "6,0,0,5,0.5,0.5,2,0.1,6,0*");
879    }
880
881    #[test]
882    fn test_thermal_primitive_codegen() {
883        let line = ThermalPrimitive {
884            center: (Value(0.0), Value(0.0)),
885            outer_diameter: Value(8.0),
886            inner_diameter: Value(6.5),
887            gap: Value(1.0),
888            angle: Value(45.0),
889        };
890        assert_partial_code!(line, "7,0,0,8,6.5,1,45*");
891    }
892
893    #[test]
894    fn test_aperture_macro_codegen() {
895        let am = ApertureMacro::new("CRAZY")
896            .add_content(MacroContent::Thermal(ThermalPrimitive {
897                center: (Value(0.0), Value(0.0)),
898                outer_diameter: Value(0.08),
899                inner_diameter: Value(0.055),
900                gap: Value(0.0125),
901                angle: Value(45.0),
902            }))
903            .add_content(MacroContent::Moire(MoirePrimitive {
904                center: (Value(0.0), Value(0.0)),
905                diameter: Value(0.125),
906                ring_thickness: Value(0.01),
907                gap: Value(0.01),
908                max_rings: 3,
909                cross_hair_thickness: Value(0.003),
910                cross_hair_length: Value(0.150),
911                angle: Value(0.0),
912            }));
913        assert_partial_code!(
914            am,
915            "AMCRAZY*\n7,0,0,0.08,0.055,0.0125,45*\n6,0,0,0.125,0.01,0.01,3,0.003,0.15,0*"
916        );
917    }
918
919    #[test]
920    fn test_codegen_with_variable() {
921        let line = VectorLinePrimitive {
922            exposure: true,
923            width: Variable(0),
924            start: (Variable(1), 0.45.into()),
925            end: (Value(12.), Variable(2)),
926            angle: Variable(3),
927        };
928        assert_partial_code!(line, "20,1,$0,$1,0.45,12,$2,$3*");
929    }
930
931    #[test]
932    fn test_macro_decimal_into() {
933        let a = Value(1.0);
934        let b: MacroDecimal = 1.0.into();
935        assert_eq!(a, b);
936        let c = Variable(1);
937        let d = Variable(1);
938        assert_eq!(c, d);
939    }
940
941    #[test]
942    fn test_comment_codegen() {
943        let comment = MacroContent::Comment("hello world".to_string());
944        assert_partial_code!(comment, "0 hello world*");
945    }
946
947    #[test]
948    fn test_variable_definition_codegen() {
949        let var = VariableDefinition {
950            number: 17,
951            expression: "$40+2".to_string(),
952        };
953        assert_partial_code!(var, "$17=$40+2*");
954    }
955
956    #[test]
957    fn test_macrocontent_from_into() {
958        let a = MacroContent::Comment("hello".into());
959        let b: MacroContent = "hello".to_string().into();
960        let c: MacroContent = "hello".into();
961        assert_eq!(a, b);
962        assert_eq!(b, c);
963    }
964
965    #[test]
966    fn test_circle_primitive_new() {
967        let c1 = CirclePrimitive::new(Value(3.0)).centered_at((Value(5.0), Value(0.0)));
968        let c2 = CirclePrimitive {
969            exposure: true,
970            diameter: Value(3.0),
971            center: (Value(5.0), Value(0.0)),
972            angle: None,
973        };
974        assert_eq!(c1, c2);
975    }
976
977    #[test]
978    fn test_vectorline_primitive_new() {
979        let vl1 = VectorLinePrimitive::new((Value(0.0), Value(5.3)), (Value(3.9), Value(8.5)))
980            .with_angle(Value(38.0));
981        let vl2 = VectorLinePrimitive {
982            exposure: true,
983            width: Value(0.0),
984            start: (Value(0.0), Value(5.3)),
985            end: (Value(3.9), Value(8.5)),
986            angle: Value(38.0),
987        };
988        assert_eq!(vl1, vl2);
989    }
990
991    #[test]
992    fn test_centerline_primitive_new() {
993        let cl1 = CenterLinePrimitive::new((Value(3.0), Value(4.5))).exposure_on(false);
994        let cl2 = CenterLinePrimitive {
995            exposure: false,
996            dimensions: (Value(3.0), Value(4.5)),
997            center: (Value(0.0), Value(0.0)),
998            angle: Value(0.0),
999        };
1000        assert_eq!(cl1, cl2);
1001    }
1002
1003    #[test]
1004    fn test_outline_primitive_new() {
1005        let op1 = OutlinePrimitive::new()
1006            .add_point((Value(0.0), Value(0.0)))
1007            .add_point((Value(2.0), Value(2.0)))
1008            .add_point((Value(-2.0), Value(-2.0)))
1009            .add_point((Value(0.0), Value(0.0)));
1010
1011        let pts = vec![
1012            (Value(0.0), Value(0.0)),
1013            (Value(2.0), Value(2.0)),
1014            (Value(-2.0), Value(-2.0)),
1015            (Value(0.0), Value(0.0)),
1016        ];
1017
1018        let op2 = OutlinePrimitive {
1019            exposure: true,
1020            points: pts,
1021            angle: Value(0.0),
1022        };
1023        assert_eq!(op1, op2);
1024    }
1025
1026    #[test]
1027    fn test_polygon_primitive_new() {
1028        let pp1 = PolygonPrimitive::new(5)
1029            .with_angle(Value(98.0))
1030            .with_diameter(Value(5.3))
1031            .centered_at((Value(1.0), Value(1.0)));
1032        let pp2 = PolygonPrimitive {
1033            exposure: true,
1034            vertices: 5,
1035            angle: Value(98.0),
1036            diameter: Value(5.3),
1037            center: (Value(1.0), Value(1.0)),
1038        };
1039        assert_eq!(pp1, pp2);
1040    }
1041
1042    #[test]
1043    fn test_moire_primitive_new() {
1044        let mp1 = MoirePrimitive::new()
1045            .with_diameter(Value(3.0))
1046            .with_ring_thickness(Value(0.05))
1047            .with_cross_thickness(Value(0.01))
1048            .with_cross_length(Value(0.5))
1049            .with_rings_max(3);
1050        let mp2 = MoirePrimitive {
1051            center: (MacroDecimal::Value(0.0), MacroDecimal::Value(0.0)),
1052            diameter: MacroDecimal::Value(3.0),
1053            ring_thickness: MacroDecimal::Value(0.05),
1054            gap: MacroDecimal::Value(0.0),
1055            max_rings: 3,
1056            cross_hair_thickness: MacroDecimal::Value(0.01),
1057            cross_hair_length: MacroDecimal::Value(0.5),
1058            angle: MacroDecimal::Value(0.0),
1059        };
1060        assert_eq!(mp1, mp2);
1061    }
1062
1063    #[test]
1064    fn test_thermal_primitive_new() {
1065        let tp1 = ThermalPrimitive::new(Value(1.0), Value(2.0), Value(1.5)).with_angle(Value(87.3));
1066        let tp2 = ThermalPrimitive {
1067            inner_diameter: Value(1.0),
1068            outer_diameter: Value(2.0),
1069            gap: Value(1.5),
1070            angle: Value(87.3),
1071            center: (Value(0.0), Value(0.0)),
1072        };
1073        assert_eq!(tp1, tp2);
1074    }
1075
1076    #[test]
1077    fn test_variabledefinition_new() {
1078        let vd1 = VariableDefinition::new(3, "Test!");
1079        let vd2 = VariableDefinition {
1080            number: 3,
1081            expression: "Test!".into(),
1082        };
1083        assert_eq!(vd1, vd2);
1084    }
1085}