gerber_types/
attributes.rs

1//! Attributes.
2
3use std::io::Write;
4use strum_macros::{IntoStaticStr, VariantArray, VariantNames};
5use uuid::Uuid;
6
7use crate::errors::GerberResult;
8use crate::traits::PartialGerberCode;
9use crate::GerberDate;
10
11#[derive(Debug, Clone, PartialEq, Eq, Hash)]
12pub enum Ident {
13    // Aka 'Guid'
14    Uuid(Uuid),
15    Name(String),
16}
17
18impl<W: Write> PartialGerberCode<W> for Ident {
19    fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
20        match self {
21            Ident::Uuid(guid) => {
22                write!(writer, "{}", guid)?;
23            }
24            Ident::Name(value) => {
25                write!(writer, "{}", value)?;
26            }
27        }
28
29        Ok(())
30    }
31}
32
33// FileAttribute
34
35#[derive(Debug, Clone, PartialEq, Eq, Hash)]
36pub enum FileAttribute {
37    /// "%TF.Part,(Single|Array|FabricationPanel|Coupon|Other,<mandatory description>)>*%
38    Part(Part),
39    /// "%TF.FileFunction,<args>*%
40    FileFunction(FileFunction),
41    /// "%TF.FilePolarity,(Positive|Negative)>*%
42    FilePolarity(FilePolarity),
43    /// "%TF.SameCoordinates[,<ident>]*%"
44    SameCoordinates(Option<Ident>),
45    /// "%TF.CreationDate,2015-02-23T15:59:51+01:00*%" ISO8601 + TZ
46    CreationDate(GerberDate),
47    /// "%TF.GenerationSoftware,<vendor>,<application>,<version>*%"
48    GenerationSoftware(GenerationSoftware),
49    /// "%TF.ProjectId,<Name>,<GUID>,<Revision>*%"
50    ProjectId {
51        id: String,
52        uuid: Uuid,
53        revision: String,
54    },
55    /// "%TF.MD5,6ab9e892830469cdff7e3e346331d404*%"
56    Md5(String),
57    UserDefined {
58        name: String,
59        values: Vec<String>,
60    },
61}
62
63impl<W: Write> PartialGerberCode<W> for FileAttribute {
64    fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
65        match self {
66            FileAttribute::Part(ref part) => {
67                write!(writer, ".Part,")?;
68                part.serialize_partial(writer)?;
69            }
70            FileAttribute::FileFunction(ref function) => {
71                write!(writer, ".FileFunction,")?;
72                match function {
73                    FileFunction::Copper {
74                        ref layer,
75                        ref pos,
76                        ref copper_type,
77                    } => {
78                        write!(writer, "Copper,L{},", layer)?;
79                        pos.serialize_partial(writer)?;
80                        if let Some(ref t) = *copper_type {
81                            write!(writer, ",")?;
82                            t.serialize_partial(writer)?;
83                        }
84                    }
85                    FileFunction::Plated {
86                        from_layer,
87                        to_layer,
88                        drill,
89                        label,
90                    } => {
91                        write!(writer, "Plated,{},{},", from_layer, to_layer)?;
92                        drill.serialize_partial(writer)?;
93                        if let Some(ref l) = label {
94                            write!(writer, ",")?;
95                            l.serialize_partial(writer)?;
96                        }
97                    }
98                    FileFunction::NonPlated {
99                        from_layer,
100                        to_layer,
101                        drill,
102                        label,
103                    } => {
104                        write!(writer, "NonPlated,{},{},", from_layer, to_layer)?;
105                        drill.serialize_partial(writer)?;
106                        if let Some(ref l) = label {
107                            write!(writer, ",")?;
108                            l.serialize_partial(writer)?;
109                        }
110                    }
111                    FileFunction::Profile(ref plating) => {
112                        write!(writer, "Profile")?;
113                        if let Some(ref plating) = plating {
114                            write!(writer, ",")?;
115                            plating.serialize_partial(writer)?;
116                        }
117                    }
118                    FileFunction::KeepOut(ref pos) => {
119                        write!(writer, "Keepout,")?;
120                        pos.serialize_partial(writer)?;
121                    }
122                    FileFunction::SolderMask { ref pos, ref index } => {
123                        write!(writer, "Soldermask,")?;
124                        pos.serialize_partial(writer)?;
125                        if let Some(ref i) = index {
126                            write!(writer, ",{}", *i)?;
127                        }
128                    }
129                    FileFunction::Legend { ref pos, ref index } => {
130                        write!(writer, "Legend,")?;
131                        pos.serialize_partial(writer)?;
132                        if let Some(ref i) = index {
133                            write!(writer, ",{}", *i)?;
134                        }
135                    }
136                    FileFunction::Component { layer, pos } => {
137                        write!(writer, "Component,L{},", layer)?;
138                        pos.serialize_partial(writer)?;
139                    }
140                    FileFunction::Paste(pos) => {
141                        write!(writer, "Paste,")?;
142                        pos.serialize_partial(writer)?;
143                    }
144                    FileFunction::Glue(pos) => {
145                        write!(writer, "Glue,")?;
146                        pos.serialize_partial(writer)?;
147                    }
148                    FileFunction::CarbonMask { pos, index } => {
149                        write!(writer, "Carbonmask,")?;
150                        pos.serialize_partial(writer)?;
151                        if let Some(ref i) = index {
152                            write!(writer, ",{}", *i)?;
153                        }
154                    }
155                    FileFunction::GoldMask { pos, index } => {
156                        write!(writer, "Goldmask,")?;
157                        pos.serialize_partial(writer)?;
158                        if let Some(ref i) = index {
159                            write!(writer, ",{}", *i)?;
160                        }
161                    }
162                    FileFunction::HeatsinkMask { pos, index } => {
163                        write!(writer, "Heatsinkmask,")?;
164                        pos.serialize_partial(writer)?;
165                        if let Some(ref i) = index {
166                            write!(writer, ",{}", *i)?;
167                        }
168                    }
169                    FileFunction::PeelableMask { pos, index } => {
170                        write!(writer, "Peelablemask,")?;
171                        pos.serialize_partial(writer)?;
172                        if let Some(ref i) = index {
173                            write!(writer, ",{}", *i)?;
174                        }
175                    }
176                    FileFunction::SilverMask { pos, index } => {
177                        write!(writer, "Silvermask,")?;
178                        pos.serialize_partial(writer)?;
179                        if let Some(ref i) = index {
180                            write!(writer, ",{}", *i)?;
181                        }
182                    }
183                    FileFunction::TinMask { pos, index } => {
184                        write!(writer, "Tinmask,")?;
185                        pos.serialize_partial(writer)?;
186                        if let Some(ref i) = index {
187                            write!(writer, ",{}", *i)?;
188                        }
189                    }
190                    FileFunction::DepthRoute(pos) => {
191                        write!(writer, "Depthrout,")?;
192                        pos.serialize_partial(writer)?;
193                    }
194                    FileFunction::VCut(pos) => {
195                        write!(writer, "Vcut")?;
196                        if let Some(pos) = pos {
197                            write!(writer, ",")?;
198                            pos.serialize_partial(writer)?;
199                        }
200                    }
201                    FileFunction::ViaFill => {
202                        write!(writer, "Viafill")?;
203                    }
204                    FileFunction::Pads(pos) => {
205                        write!(writer, "Pads,")?;
206                        pos.serialize_partial(writer)?;
207                    }
208                    FileFunction::Other(value) => {
209                        write!(writer, "Other,{}", value)?;
210                    }
211
212                    // "Drawing layers"
213                    FileFunction::DrillMap => {
214                        write!(writer, "Drillmap")?;
215                    }
216                    FileFunction::FabricationDrawing => {
217                        write!(writer, "FabricationDrawing")?;
218                    }
219                    FileFunction::VCutMap => {
220                        write!(writer, "Vcutmap")?;
221                    }
222                    FileFunction::AssemblyDrawing(pos) => {
223                        write!(writer, "AssemblyDrawing,")?;
224                        pos.serialize_partial(writer)?;
225                    }
226                    FileFunction::ArrayDrawing => {
227                        write!(writer, "ArrayDrawing")?;
228                    }
229                    FileFunction::OtherDrawing(value) => {
230                        write!(writer, "OtherDrawing,{}", value)?;
231                    }
232                }
233            }
234            FileAttribute::FilePolarity(ref p) => {
235                write!(writer, ".FilePolarity,")?;
236                p.serialize_partial(writer)?;
237            }
238            FileAttribute::SameCoordinates(ident) => {
239                write!(writer, ".SameCoordinates")?;
240                if let Some(ident) = ident {
241                    write!(writer, ",")?;
242                    ident.serialize_partial(writer)?;
243                }
244            }
245            FileAttribute::CreationDate(date) => {
246                write!(writer, ".CreationDate,{}", date.to_rfc3339())?;
247            }
248            FileAttribute::GenerationSoftware(ref gs) => {
249                write!(writer, ".GenerationSoftware,")?;
250                gs.serialize_partial(writer)?;
251            }
252            FileAttribute::ProjectId { id, uuid, revision } => {
253                write!(writer, ".ProjectId,{},{},{}", id, uuid, revision)?;
254            }
255            FileAttribute::Md5(ref hash) => write!(writer, ".MD5,{}", hash)?,
256            FileAttribute::UserDefined { name, values } => {
257                write!(writer, "{}", name)?;
258                for value in values {
259                    write!(writer, ",{}", value)?;
260                }
261            }
262        };
263        Ok(())
264    }
265}
266
267// TextMode
268#[derive(Debug, Copy, Clone, PartialEq, Hash, IntoStaticStr, VariantNames, VariantArray)]
269#[strum(serialize_all = "UPPERCASE")]
270pub enum TextMode {
271    #[strum(serialize = "B")]
272    BarCode,
273    #[strum(serialize = "C")]
274    Characters,
275}
276
277impl_partial_gerber_code_via_strum!(TextMode);
278
279// TextMirroring
280#[derive(Debug, Copy, Clone, PartialEq, Hash, IntoStaticStr, VariantNames, VariantArray)]
281#[strum(serialize_all = "UPPERCASE")]
282pub enum TextMirroring {
283    #[strum(serialize = "R")]
284    Readable,
285    #[strum(serialize = "M")]
286    Mirrored,
287}
288
289impl_partial_gerber_code_via_strum!(TextMirroring);
290
291// ApertureAttribute
292
293#[derive(Debug, Clone, PartialEq)]
294pub enum ApertureAttribute {
295    ApertureFunction(ApertureFunction),
296    DrillTolerance {
297        plus: f64,
298        minus: f64,
299    },
300    FlashText {
301        text: String,
302        mode: TextMode,
303        mirroring: Option<TextMirroring>,
304        font: Option<String>,
305        size: Option<i32>,
306        comment: Option<String>,
307    },
308    UserDefined {
309        name: String,
310        values: Vec<String>,
311    },
312}
313
314impl<W: Write> PartialGerberCode<W> for ApertureAttribute {
315    fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
316        match self {
317            ApertureAttribute::ApertureFunction(ref af) => {
318                write!(writer, ".AperFunction,")?;
319                match af {
320                    // "Drill and rout layers"
321                    ApertureFunction::ViaDrill(ref value) => {
322                        write!(writer, "ViaDrill")?;
323                        if let Some(value) = value {
324                            write!(writer, ",")?;
325                            value.serialize_partial(writer)?;
326                        }
327                    }
328                    ApertureFunction::BackDrill => {
329                        write!(writer, "BackDrill")?;
330                    }
331                    ApertureFunction::ComponentDrill { ref function } => {
332                        write!(writer, "ComponentDrill")?;
333                        if let Some(function) = function {
334                            write!(writer, ",")?;
335                            function.serialize_partial(writer)?;
336                        }
337                    }
338                    ApertureFunction::MechanicalDrill { function } => {
339                        write!(writer, "MechanicalDrill")?;
340                        if let Some(ref function) = function {
341                            write!(writer, ",")?;
342                            function.serialize_partial(writer)?;
343                        }
344                    }
345                    ApertureFunction::CastellatedDrill => {
346                        write!(writer, "CastellatedDrill")?;
347                    }
348                    ApertureFunction::OtherDrill(ref value) => {
349                        write!(writer, "OtherDrill,{}", value)?;
350                    }
351
352                    // "Copper layers"
353                    ApertureFunction::ComponentPad => {
354                        write!(writer, "ComponentPad")?;
355                    }
356                    ApertureFunction::SmdPad(ref value) => {
357                        write!(writer, "SMDPad,")?;
358                        value.serialize_partial(writer)?;
359                    }
360                    ApertureFunction::BgaPad(ref value) => {
361                        write!(writer, "BGAPad,")?;
362                        value.serialize_partial(writer)?;
363                    }
364                    ApertureFunction::ConnectorPad => {
365                        write!(writer, "ConnectorPad")?;
366                    }
367                    ApertureFunction::HeatsinkPad => {
368                        write!(writer, "HeatsinkPad")?;
369                    }
370                    ApertureFunction::ViaPad => {
371                        write!(writer, "ViaPad")?;
372                    }
373                    ApertureFunction::TestPad => {
374                        write!(writer, "TestPad")?;
375                    }
376                    ApertureFunction::CastellatedPad => {
377                        write!(writer, "CastellatedPad")?;
378                    }
379                    ApertureFunction::FiducialPad(ref value) => {
380                        write!(writer, "FiducialPad,")?;
381                        value.serialize_partial(writer)?;
382                    }
383                    ApertureFunction::ThermalReliefPad => {
384                        write!(writer, "ThermalReliefPad")?;
385                    }
386                    ApertureFunction::WasherPad => {
387                        write!(writer, "WasherPad")?;
388                    }
389                    ApertureFunction::AntiPad => {
390                        write!(writer, "AntiPad")?;
391                    }
392                    ApertureFunction::OtherPad(ref value) => {
393                        write!(writer, "OtherPad,{}", value)?;
394                    }
395                    ApertureFunction::Conductor => {
396                        write!(writer, "Conductor")?;
397                    }
398                    ApertureFunction::EtchedComponent => {
399                        write!(writer, "EtchedComponent")?;
400                    }
401                    ApertureFunction::NonConductor => {
402                        write!(writer, "NonConductor")?;
403                    }
404                    ApertureFunction::CopperBalancing => {
405                        write!(writer, "CopperBalancing")?;
406                    }
407                    ApertureFunction::Border => {
408                        write!(writer, "Border")?;
409                    }
410                    ApertureFunction::OtherCopper(ref value) => {
411                        write!(writer, "OtherCopper,{}", value)?;
412                    }
413
414                    // "Component layers"
415                    ApertureFunction::ComponentMain => {
416                        write!(writer, "ComponentMain")?;
417                    }
418                    ApertureFunction::ComponentOutline(ref value) => {
419                        write!(writer, "ComponentOutline,")?;
420                        value.serialize_partial(writer)?;
421                    }
422                    ApertureFunction::ComponentPin => {
423                        write!(writer, "ComponentPin")?;
424                    }
425
426                    // "All data layers"
427                    ApertureFunction::Profile => {
428                        write!(writer, "Profile")?;
429                    }
430                    ApertureFunction::NonMaterial => {
431                        write!(writer, "NonMaterial")?;
432                    }
433                    ApertureFunction::Material => {
434                        write!(writer, "Material")?;
435                    }
436                    ApertureFunction::Other(value) => {
437                        write!(writer, "Other,{}", value)?;
438                    }
439
440                    // 2024.05 - 8.4 - "Deprecated attribute values"
441                    ApertureFunction::Slot => {
442                        write!(writer, "Slot")?;
443                    }
444                    ApertureFunction::Cavity => {
445                        write!(writer, "Cavity")?;
446                    }
447                    ApertureFunction::CutOut => {
448                        write!(writer, "CutOut")?;
449                    }
450                    ApertureFunction::Drawing => {
451                        write!(writer, "Drawing")?;
452                    }
453                }
454            }
455            ApertureAttribute::DrillTolerance { plus, minus } => {
456                write!(writer, ".DrillTolerance,{},{}", plus, minus)?;
457            }
458            ApertureAttribute::FlashText {
459                text,
460                mode,
461                mirroring,
462                font,
463                size,
464                comment,
465            } => {
466                write!(writer, ".FlashText,{},", text)?;
467                mode.serialize_partial(writer)?;
468                write!(writer, ",")?;
469                mirroring.serialize_partial(writer)?;
470                write!(writer, ",")?;
471                if let Some(font) = font {
472                    write!(writer, "{}", font)?;
473                }
474                write!(writer, ",")?;
475                if let Some(size) = size {
476                    write!(writer, "{}", size)?;
477                }
478                write!(writer, ",")?;
479                if let Some(comment) = comment {
480                    write!(writer, "{}", comment)?;
481                }
482            }
483            ApertureAttribute::UserDefined { name, values } => {
484                write!(writer, "{}", name)?;
485                for value in values {
486                    write!(writer, ",{}", value)?;
487                }
488            }
489        }
490        Ok(())
491    }
492}
493
494// Part
495
496#[derive(Debug, Clone, PartialEq, Eq, Hash)]
497pub enum Part {
498    /// Single PCB
499    Single,
500    /// A.k.a. customer panel, assembly panel, shipping panel, biscuit
501    Array,
502    /// A.k.a. working panel, production panel
503    FabricationPanel,
504    /// A test coupon
505    Coupon,
506    /// None of the above
507    Other(String),
508}
509
510impl<W: Write> PartialGerberCode<W> for Part {
511    fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
512        match *self {
513            Part::Single => write!(writer, "Single")?,
514            Part::Array => write!(writer, "Array")?,
515            Part::FabricationPanel => write!(writer, "FabricationPanel")?,
516            Part::Coupon => write!(writer, "Coupon")?,
517            Part::Other(ref description) => write!(writer, "Other,{}", description)?,
518        };
519        Ok(())
520    }
521}
522
523// Position
524
525#[derive(
526    Debug,
527    Copy,
528    Clone,
529    PartialEq,
530    Eq,
531    Hash,
532    strum_macros::Display,
533    IntoStaticStr,
534    VariantNames,
535    VariantArray,
536)]
537#[strum(serialize_all = "PascalCase")]
538pub enum Position {
539    Top,
540    #[strum(serialize = "Bot")]
541    Bottom,
542}
543
544impl_partial_gerber_code_via_strum!(Position);
545
546// ExtendedPosition
547
548#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, IntoStaticStr, VariantNames, VariantArray)]
549#[strum(serialize_all = "PascalCase")]
550pub enum ExtendedPosition {
551    Top,
552    #[strum(serialize = "Inr")]
553    Inner,
554    #[strum(serialize = "Bot")]
555    Bottom,
556}
557
558impl_partial_gerber_code_via_strum!(ExtendedPosition);
559
560// CopperType
561
562#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, IntoStaticStr, VariantNames, VariantArray)]
563#[strum(serialize_all = "PascalCase")]
564pub enum CopperType {
565    Plane,
566    Signal,
567    Mixed,
568    Hatched,
569}
570
571impl_partial_gerber_code_via_strum!(CopperType);
572
573// PlatedDrill
574
575#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, IntoStaticStr, VariantNames, VariantArray)]
576#[strum(serialize_all = "PascalCase")]
577pub enum PlatedDrill {
578    #[strum(serialize = "PTH")]
579    PlatedThroughHole,
580    Blind,
581    Buried,
582}
583
584impl_partial_gerber_code_via_strum!(PlatedDrill);
585
586// NonPlatedDrill
587
588#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, IntoStaticStr, VariantNames, VariantArray)]
589#[strum(serialize_all = "PascalCase")]
590pub enum NonPlatedDrill {
591    #[strum(serialize = "NPTH")]
592    NonPlatedThroughHole,
593    Blind,
594    Buried,
595}
596
597impl_partial_gerber_code_via_strum!(NonPlatedDrill);
598
599// DrillRouteType
600
601#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, IntoStaticStr, VariantNames, VariantArray)]
602#[strum(serialize_all = "PascalCase")]
603pub enum DrillRouteType {
604    Drill,
605    #[strum(serialize = "Rout")]
606    Route,
607    Mixed,
608}
609
610impl_partial_gerber_code_via_strum!(DrillRouteType);
611
612// Profile
613
614#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, IntoStaticStr, VariantNames, VariantArray)]
615pub enum Profile {
616    #[strum(serialize = "P")]
617    Plated,
618    #[strum(serialize = "NP")]
619    NonPlated,
620}
621
622impl_partial_gerber_code_via_strum!(Profile);
623
624// FileFunction
625
626#[derive(Debug, Clone, PartialEq, Eq, Hash)]
627pub enum FileFunction {
628    //
629    // "Data layers"
630    //
631    Copper {
632        layer: i32,
633        pos: ExtendedPosition,
634        copper_type: Option<CopperType>,
635    },
636    Plated {
637        from_layer: i32,
638        to_layer: i32,
639        drill: PlatedDrill,
640        label: Option<DrillRouteType>,
641    },
642    NonPlated {
643        from_layer: i32,
644        to_layer: i32,
645        drill: NonPlatedDrill,
646        label: Option<DrillRouteType>,
647    },
648    /// Apparently, this should be used instead of `KeepOut` since 2017.11, see "11.15 Revision 2017.11" but this makes no sense
649    /// Since keep-out has a `Position` but Profile does not...
650    /// Additionally, DipTrace does not specify the 'N/NP', e.g. "%TF.FileFunction,Profile*%"
651    Profile(Option<Profile>),
652    KeepOut(Position),
653    SolderMask {
654        pos: Position,
655        index: Option<i32>,
656    },
657    Legend {
658        pos: Position,
659        index: Option<i32>,
660    },
661    Component {
662        layer: i32,
663        pos: Position,
664    },
665    Paste(Position),
666    Glue(Position),
667    CarbonMask {
668        pos: Position,
669        index: Option<i32>,
670    },
671    GoldMask {
672        pos: Position,
673        index: Option<i32>,
674    },
675    HeatsinkMask {
676        pos: Position,
677        index: Option<i32>,
678    },
679    PeelableMask {
680        pos: Position,
681        index: Option<i32>,
682    },
683    SilverMask {
684        pos: Position,
685        index: Option<i32>,
686    },
687    TinMask {
688        pos: Position,
689        index: Option<i32>,
690    },
691    DepthRoute(Position),
692    VCut(Option<Position>),
693    /// Contains the via’s that must be filled (usually with some form of epoxy)
694    ViaFill,
695    Pads(Position),
696    Other(String),
697
698    // "Drawing layers"
699    DrillMap,
700    FabricationDrawing,
701    VCutMap,
702    AssemblyDrawing(Position),
703    ArrayDrawing,
704    OtherDrawing(String),
705}
706
707// FilePolarity
708
709#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, IntoStaticStr, VariantNames, VariantArray)]
710#[strum(serialize_all = "PascalCase")]
711pub enum FilePolarity {
712    Positive,
713    Negative,
714}
715
716impl_partial_gerber_code_via_strum!(FilePolarity);
717
718// GenerationSoftware
719
720#[derive(Debug, Clone, PartialEq, Eq, Hash)]
721pub struct GenerationSoftware {
722    pub vendor: String,
723    pub application: String,
724    pub version: Option<String>,
725}
726
727impl GenerationSoftware {
728    pub fn new<S: Into<String>>(vendor: S, application: S, version: Option<S>) -> Self {
729        GenerationSoftware {
730            vendor: vendor.into(),
731            application: application.into(),
732            version: version.map(|s| s.into()),
733        }
734    }
735}
736
737impl<W: Write> PartialGerberCode<W> for GenerationSoftware {
738    fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
739        match self.version {
740            Some(ref v) => write!(writer, "{},{},{}", self.vendor, self.application, v)?,
741            None => write!(writer, "{},{}", self.vendor, self.application)?,
742        };
743        Ok(())
744    }
745}
746
747/// ApertureFunction
748///
749/// 2024.05 - 5.6.10 ".AperFunction"
750#[derive(Debug, Clone, PartialEq, Eq, Hash)]
751pub enum ApertureFunction {
752    // "Drill and rout layers"
753    ViaDrill(Option<IPC4761ViaProtection>),
754    BackDrill,
755    ComponentDrill { function: Option<ComponentDrill> },
756    MechanicalDrill { function: Option<DrillFunction> },
757    CastellatedDrill,
758    OtherDrill(String),
759
760    // "Copper layers"
761    ComponentPad,
762    SmdPad(SmdPadType),
763    BgaPad(SmdPadType),
764    ConnectorPad,
765    HeatsinkPad,
766    ViaPad,
767    TestPad,
768    CastellatedPad,
769    FiducialPad(FiducialScope),
770    ThermalReliefPad,
771    WasherPad,
772    AntiPad,
773    OtherPad(String),
774    Conductor,
775    EtchedComponent,
776    NonConductor,
777    CopperBalancing,
778    Border,
779    OtherCopper(String),
780
781    // "All data layers"
782    Profile,
783    Material,
784    NonMaterial,
785    Other(String),
786
787    // "Component layers"
788    ComponentMain,
789    ComponentOutline(ComponentOutline),
790    ComponentPin,
791
792    // 2024.05 - 8.4 - "Deprecated attribute values"
793    Slot,
794    CutOut,
795    Cavity,
796    Drawing,
797}
798
799#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, IntoStaticStr, VariantNames, VariantArray)]
800#[strum(serialize_all = "PascalCase")]
801pub enum IPC4761ViaProtection {
802    Ia,
803    Ib,
804    IIa,
805    IIb,
806    #[strum(serialize = "IIIa")]
807    IIIa,
808    #[strum(serialize = "IIIb")]
809    IIIb,
810    IVa,
811    IVb,
812    V,
813    #[strum(serialize = "VI")]
814    VI,
815    #[strum(serialize = "VII")]
816    VII,
817    None,
818}
819
820impl_partial_gerber_code_via_strum!(IPC4761ViaProtection);
821
822#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, IntoStaticStr, VariantNames, VariantArray)]
823#[strum(serialize_all = "PascalCase")]
824pub enum ComponentOutline {
825    Body,
826    Lead2Lead,
827    Footprint,
828    Courtyard,
829}
830
831impl_partial_gerber_code_via_strum!(ComponentOutline);
832
833// DrillFunction
834
835#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, IntoStaticStr, VariantNames, VariantArray)]
836#[strum(serialize_all = "PascalCase")]
837pub enum DrillFunction {
838    #[strum(serialize = "Breakout")]
839    BreakOut,
840    Tooling,
841    Other,
842}
843
844impl_partial_gerber_code_via_strum!(DrillFunction);
845
846// ComponentDrill
847
848/// 2024.05 spec mismatch warning: Aperture function ".AperFunction.ComponentDill" has "PressFit" (uppercase F) whereas Component Characteristics ".CMnt" has "Pressfit" (lowercase f)"
849#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, IntoStaticStr, VariantNames, VariantArray)]
850pub enum ComponentDrill {
851    #[strum(serialize = "PressFit")]
852    PressFit,
853}
854
855impl_partial_gerber_code_via_strum!(ComponentDrill);
856
857// SmdPadType
858
859#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, IntoStaticStr, VariantNames, VariantArray)]
860#[strum(serialize_all = "PascalCase")]
861pub enum SmdPadType {
862    #[strum(serialize = "CuDef")]
863    CopperDefined,
864    #[strum(serialize = "SMDef")]
865    SoldermaskDefined,
866}
867
868impl_partial_gerber_code_via_strum!(SmdPadType);
869
870// FiducialScope
871
872#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, IntoStaticStr, VariantNames, VariantArray)]
873#[strum(serialize_all = "PascalCase")]
874pub enum FiducialScope {
875    Local,
876    Global,
877    Panel,
878}
879
880impl_partial_gerber_code_via_strum!(FiducialScope);
881
882// ObjectAttribute
883#[derive(Debug, Clone, PartialEq)]
884pub enum ObjectAttribute {
885    Net(Net),
886    Pin(Pin),
887    /// aka 'RefDes'
888    Component(String),
889    ComponentCharacteristics(ComponentCharacteristics),
890    UserDefined {
891        name: String,
892        values: Vec<String>,
893    },
894}
895
896impl<W: Write> PartialGerberCode<W> for ObjectAttribute {
897    fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
898        match self {
899            ObjectAttribute::Net(net) => {
900                net.serialize_partial(writer)?;
901            }
902            ObjectAttribute::Pin(pin) => {
903                pin.serialize_partial(writer)?;
904            }
905            ObjectAttribute::Component(ref_des) => {
906                write!(writer, ".C,{}", ref_des)?;
907            }
908            ObjectAttribute::ComponentCharacteristics(cc) => {
909                cc.serialize_partial(writer)?;
910            }
911            ObjectAttribute::UserDefined { name, values } => {
912                write!(writer, "{}", name)?;
913                for value in values {
914                    write!(writer, ",{}", value)?;
915                }
916            }
917        };
918        Ok(())
919    }
920}
921
922/// ComponentCharacteristics
923/// 2024.05 - 5.6.1.6 "Cxxx (Component Characteristics)"
924#[derive(Debug, Clone, PartialEq)]
925pub enum ComponentCharacteristics {
926    /// ".CRot,<decimal>"
927    Rotation(f64),
928    /// ".CMfr,<field>"
929    Manufacturer(String),
930    /// ".CMPN,<field>"
931    MPN(String),
932    /// ".CVal,<field>"
933    Value(String),
934    /// ".CMnt,(TH|SMD|Pressfit|Other)"
935    Mount(ComponentMounting),
936    /// ".CFtp,<field>"
937    Footprint(String),
938    /// ".CPgN,<field>"
939    PackageName(String),
940    /// ".CPgD,<field>"
941    PackageDescription(String),
942    /// ".CHgt,<decimal>"
943    Height(f64),
944    /// ".CLbN,<field>"
945    LibraryName(String),
946    /// ".CLbD,<field>"
947    LibraryDescription(String),
948    /// The specification requires at least one supplier part.  Do not add the attribute if there are no supplier parts.
949    /// ".CSup,<SN>,<SPN>,{<SN>,<SPN>}"
950    Supplier(Vec<SupplierPart>),
951}
952
953impl<W: Write> PartialGerberCode<W> for ComponentCharacteristics {
954    fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
955        match self {
956            ComponentCharacteristics::Rotation(rotation) => {
957                write!(writer, ".CRot,{}", rotation)?;
958            }
959            ComponentCharacteristics::Manufacturer(manufacturer) => {
960                write!(writer, ".CMfr,{}", manufacturer)?;
961            }
962            ComponentCharacteristics::MPN(mpn) => {
963                write!(writer, ".CMPN,{}", mpn)?;
964            }
965            ComponentCharacteristics::Value(value) => {
966                write!(writer, ".CVal,{}", value)?;
967            }
968            ComponentCharacteristics::Mount(mount) => {
969                write!(writer, ".CMnt,")?;
970                mount.serialize_partial(writer)?;
971            }
972            ComponentCharacteristics::Footprint(footprint) => {
973                write!(writer, ".CFtp,{}", footprint)?;
974            }
975            ComponentCharacteristics::PackageName(package_name) => {
976                write!(writer, ".CPgN,{}", package_name)?;
977            }
978            ComponentCharacteristics::PackageDescription(package_description) => {
979                write!(writer, ".CPgD,{}", package_description)?;
980            }
981            ComponentCharacteristics::Height(height) => {
982                write!(writer, ".CHgt,{}", height)?;
983            }
984            ComponentCharacteristics::LibraryName(library_name) => {
985                write!(writer, ".CLbN,{}", library_name)?;
986            }
987            ComponentCharacteristics::LibraryDescription(library_description) => {
988                write!(writer, ".CLbD,{}", library_description)?;
989            }
990            ComponentCharacteristics::Supplier(values) => {
991                write!(writer, ".CSup")?;
992                for value in values {
993                    write!(writer, ",")?;
994                    value.serialize_partial(writer)?;
995                }
996            }
997        }
998        Ok(())
999    }
1000}
1001
1002/// 2024.05 spec mismatch warning: Aperture function ".AperFunction.ComponentDill" has "PressFit" (uppercase F) whereas Component Characteristics ".CMnt" has "Pressfit" (lowercase f)"
1003#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, IntoStaticStr, VariantNames, VariantArray)]
1004pub enum ComponentMounting {
1005    #[strum(serialize = "TH")]
1006    ThroughHole,
1007    /// Surface mount device
1008    #[strum(serialize = "SMD")]
1009    SMD,
1010    #[strum(serialize = "Pressfit")]
1011    PressFit,
1012    #[strum(serialize = "Other")]
1013    Other,
1014}
1015
1016impl_partial_gerber_code_via_strum!(ComponentMounting);
1017
1018#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1019pub struct SupplierPart {
1020    /// The name of the supplier, e.g. 'Mouser', 'Digikey', 'LCSC', etc.
1021    pub supplier_name: String,
1022    /// The spec says 'supplier part name' but using 'reference' is more accurate, as that is what they use to look up the part
1023    pub supplier_part_reference: String,
1024}
1025
1026impl<W: Write> PartialGerberCode<W> for SupplierPart {
1027    fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
1028        write!(
1029            writer,
1030            "{},{}",
1031            self.supplier_name, self.supplier_part_reference
1032        )?;
1033        Ok(())
1034    }
1035}
1036
1037#[derive(Debug, Clone, PartialEq, Eq)]
1038pub enum Net {
1039    None,
1040    NotConnected,
1041    Connected(Vec<String>),
1042}
1043
1044impl<W: Write> PartialGerberCode<W> for Net {
1045    fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
1046        write!(writer, ".N,")?;
1047        match self {
1048            Net::None => {}
1049            Net::NotConnected => {
1050                write!(writer, "N/C")?;
1051            }
1052            Net::Connected(nets) => {
1053                write!(writer, "{}", nets.join(","))?;
1054            }
1055        }
1056        Ok(())
1057    }
1058}
1059
1060#[derive(Debug, Clone, PartialEq, Eq)]
1061pub struct Pin {
1062    pub refdes: String,
1063    /// 2024.05 spec calls this 'number' but a) it's defined as a string, and b) pins like 'EP' (for exposed pad) are not numbers.
1064    pub name: String,
1065    pub function: Option<String>,
1066}
1067
1068impl<W: Write> PartialGerberCode<W> for Pin {
1069    fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
1070        write!(writer, ".P,{},{}", self.refdes, self.name)?;
1071        if let Some(function) = &self.function {
1072            write!(writer, ",{}", function)?;
1073        }
1074        Ok(())
1075    }
1076}
1077
1078/// Gerber 2024.05 spec says:
1079/// "The TD command deletes ONE or ALL aperture or object attributes from the attributes dictionary."
1080/// and:
1081/// "The <AttributeName> is the name of the attribute to delete. If omitted, all aperture and object
1082/// attributes in the dictionary are deleted."
1083/// and:
1084/// "File attributes are immutable and are not deleted".
1085///
1086/// * Capitalization added for effect.
1087#[derive(Debug, Clone, PartialEq)]
1088pub enum AttributeDeletionCriterion {
1089    AllApertureAndObjectAttributes,
1090    SingleObjectAttribute(String),
1091    SingleApertureAttribute(String),
1092}
1093
1094impl<W: Write> PartialGerberCode<W> for AttributeDeletionCriterion {
1095    fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
1096        match self {
1097            AttributeDeletionCriterion::AllApertureAndObjectAttributes => {}
1098            AttributeDeletionCriterion::SingleObjectAttribute(name) => {
1099                write!(writer, "{}", name)?;
1100            }
1101            AttributeDeletionCriterion::SingleApertureAttribute(name) => {
1102                write!(writer, "{}", name)?;
1103            }
1104        }
1105        Ok(())
1106    }
1107}