gerber_types/
lib.rs

1//! # Gerber commands
2//!
3//! This crate implements the basic building blocks of Gerber (RS-274X, aka
4//! Extended Gerber version 2) code. It focusses on the low level types and does
5//! not do any semantic checking.
6//!
7//! For example, you can use an aperture without defining it. This will
8//! generate syntactically valid but semantially invalid Gerber code, but this
9//! module won't complain.
10//!
11//! ## Traits: GerberCode and PartialGerberCode
12//!
13//! There are two main traits that are used for code generation:
14//!
15//! - [`GerberCode`](trait.GerberCode.html) generates a full Gerber code line,
16//!   terminated with a newline character.
17//! - `PartialGerberCode` (internal only) generates Gerber representation of a
18//!   value, but does not represent a full line of code.
19#![allow(clippy::new_without_default)]
20
21#[cfg(test)]
22#[macro_use]
23mod test_macros;
24
25#[macro_use]
26mod serialization_macros;
27
28mod attributes;
29mod codegen;
30mod coordinates;
31mod errors;
32mod extended_codes;
33mod function_codes;
34mod macros;
35mod traits;
36mod types;
37
38pub use crate::attributes::*;
39pub use crate::coordinates::*;
40pub use crate::errors::*;
41pub use crate::extended_codes::*;
42pub use crate::function_codes::*;
43pub use crate::macros::*;
44pub use crate::traits::GerberCode;
45pub use crate::types::*;
46
47// re-export some types
48pub use uuid::Uuid;
49
50#[cfg(test)]
51mod serialization_tests {
52    use super::traits::PartialGerberCode;
53    use super::*;
54    use std::io::BufWriter;
55    use uuid::Uuid;
56
57    #[test]
58    fn test_comment() {
59        //! The serialize method of the GerberCode trait should generate strings.
60        let comment = GCode::Comment(CommentContent::String("testcomment".to_string()));
61        assert_code!(comment, "G04 testcomment*\n");
62    }
63
64    /// `standard comment` is a term defined in the gerber spec. See `2024.05 4.1 Comment (G04)`
65    #[test]
66    fn test_standard_comment_with_standard_attributes() {
67        //! Attributes should be able to be stored in G04 comments starting with `#@!`
68        let comment = GCode::Comment(CommentContent::Standard(
69            StandardComment::ApertureAttribute(ApertureAttribute::ApertureFunction(
70                ApertureFunction::SmdPad(SmdPadType::CopperDefined),
71            )),
72        ));
73        assert_code!(comment, "G04 #@! TA.AperFunction,SMDPad,CuDef*\n");
74
75        let comment = GCode::Comment(CommentContent::Standard(StandardComment::FileAttribute(
76            FileAttribute::FileFunction(FileFunction::Profile(Some(Profile::NonPlated))),
77        )));
78        assert_code!(comment, "G04 #@! TF.FileFunction,Profile,NP*\n");
79
80        let comment = GCode::Comment(CommentContent::Standard(StandardComment::ObjectAttribute(
81            ObjectAttribute::Component("R1".to_string()),
82        )));
83        assert_code!(comment, "G04 #@! TO.C,R1*\n");
84
85        let comment = GCode::Comment(CommentContent::Standard(StandardComment::DeleteAttribute(
86            AttributeDeletionCriterion::SingleApertureAttribute(".AperFunction".to_string()),
87        )));
88        assert_code!(comment, "G04 #@! TD.AperFunction*\n");
89    }
90
91    #[test]
92    fn test_standard_comment_with_custom_attributes() {
93        // custom attributes are not prefixed with a `.`.
94        let comment = GCode::Comment(CommentContent::Standard(
95            StandardComment::ApertureAttribute(ApertureAttribute::UserDefined {
96                name: "Example".to_string(),
97                values: vec!["value1".to_string(), "value2".to_string()],
98            }),
99        ));
100        assert_code!(comment, "G04 #@! TAExample,value1,value2*\n");
101
102        let comment = GCode::Comment(CommentContent::Standard(StandardComment::FileAttribute(
103            FileAttribute::UserDefined {
104                name: "Example".to_string(),
105                values: vec!["value1".to_string(), "value2".to_string()],
106            },
107        )));
108        assert_code!(comment, "G04 #@! TFExample,value1,value2*\n");
109
110        let comment = GCode::Comment(CommentContent::Standard(StandardComment::ObjectAttribute(
111            ObjectAttribute::UserDefined {
112                name: "Example".to_string(),
113                values: vec!["value1".to_string(), "value2".to_string()],
114            },
115        )));
116        assert_code!(comment, "G04 #@! TOExample,value1,value2*\n");
117
118        let comment = GCode::Comment(CommentContent::Standard(StandardComment::DeleteAttribute(
119            AttributeDeletionCriterion::SingleApertureAttribute("Example".to_string()),
120        )));
121        assert_code!(comment, "G04 #@! TDExample*\n");
122    }
123
124    #[test]
125    fn test_vec_of_comments() {
126        //! A `Vec<T: GerberCode>` should also implement `GerberCode`.
127        let mut v = Vec::new();
128        v.push(GCode::Comment(CommentContent::String(
129            "comment 1".to_string(),
130        )));
131        v.push(GCode::Comment(CommentContent::String(
132            "another one".to_string(),
133        )));
134        assert_code!(v, "G04 comment 1*\nG04 another one*\n");
135    }
136
137    #[test]
138    fn test_single_command() {
139        //! A `Command` should implement `GerberCode`
140        let c = Command::FunctionCode(FunctionCode::GCode(GCode::Comment(CommentContent::String(
141            "comment".to_string(),
142        ))));
143        assert_code!(c, "G04 comment*\n");
144    }
145
146    #[test]
147    fn test_interpolation_mode() {
148        let mut commands = Vec::new();
149        let c1 = GCode::InterpolationMode(InterpolationMode::Linear);
150        let c2 = GCode::InterpolationMode(InterpolationMode::ClockwiseCircular);
151        let c3 = GCode::InterpolationMode(InterpolationMode::CounterclockwiseCircular);
152        commands.push(c1);
153        commands.push(c2);
154        commands.push(c3);
155        assert_code!(commands, "G01*\nG02*\nG03*\n");
156    }
157
158    #[test]
159    fn test_region_mode() {
160        let mut commands = Vec::new();
161        commands.push(GCode::RegionMode(true));
162        commands.push(GCode::RegionMode(false));
163        assert_code!(commands, "G36*\nG37*\n");
164    }
165
166    #[test]
167    fn test_quadrant_mode() {
168        let mut commands = Vec::new();
169        commands.push(GCode::QuadrantMode(QuadrantMode::Single));
170        commands.push(GCode::QuadrantMode(QuadrantMode::Multi));
171        assert_code!(commands, "G74*\nG75*\n");
172    }
173
174    #[test]
175    fn test_end_of_file() {
176        let c = MCode::EndOfFile;
177        assert_code!(c, "M02*\n");
178    }
179
180    #[test]
181    fn test_operation_interpolate() {
182        let cf = CoordinateFormat::new(ZeroOmission::Leading, CoordinateMode::Absolute, 2, 5);
183        let c1 = Operation::Interpolate(
184            Some(Coordinates::new(1, 2, cf)),
185            Some(CoordinateOffset::new(5, 10, cf)),
186        );
187        assert_code!(c1, "X100000Y200000I500000J1000000D01*\n");
188        let c2 = Operation::Interpolate(
189            Some(Coordinates::at_y(
190                -2,
191                CoordinateFormat::new(ZeroOmission::Leading, CoordinateMode::Absolute, 4, 4),
192            )),
193            None,
194        );
195        assert_code!(c2, "Y-20000D01*\n");
196        let cf = CoordinateFormat::new(ZeroOmission::Leading, CoordinateMode::Absolute, 4, 4);
197        let c3 = Operation::Interpolate(
198            Some(Coordinates::at_x(1, cf)),
199            Some(CoordinateOffset::at_y(2, cf)),
200        );
201        assert_code!(c3, "X10000J20000D01*\n");
202    }
203
204    #[test]
205    fn test_operation_move() {
206        let c = Operation::Move(Some(Coordinates::new(
207            23,
208            42,
209            CoordinateFormat::new(ZeroOmission::Leading, CoordinateMode::Absolute, 6, 4),
210        )));
211        assert_code!(c, "X230000Y420000D02*\n");
212    }
213
214    #[test]
215    fn test_operation_flash() {
216        let c = Operation::Flash(Some(Coordinates::new(
217            23,
218            42,
219            CoordinateFormat::new(ZeroOmission::Leading, CoordinateMode::Absolute, 4, 4),
220        )));
221        assert_code!(c, "X230000Y420000D03*\n");
222    }
223
224    #[test]
225    fn test_select_aperture() {
226        let c1 = DCode::SelectAperture(10);
227        assert_code!(c1, "D10*\n");
228        let c2 = DCode::SelectAperture(2147483647);
229        assert_code!(c2, "D2147483647*\n");
230    }
231
232    #[test]
233    fn test_coordinate_format_tz() {
234        let c = ExtendedCode::CoordinateFormat(CoordinateFormat::new(
235            ZeroOmission::Leading,
236            CoordinateMode::Absolute,
237            2,
238            5,
239        ));
240        assert_code!(c, "%FSLAX25Y25*%\n");
241    }
242
243    #[test]
244    fn test_coordinate_format_lz() {
245        let c = ExtendedCode::CoordinateFormat(CoordinateFormat::new(
246            ZeroOmission::Trailing,
247            CoordinateMode::Incremental,
248            2,
249            5,
250        ));
251        assert_code!(c, "%FSTIX25Y25*%\n");
252    }
253
254    #[test]
255    fn test_unit() {
256        let c1 = ExtendedCode::Unit(Unit::Millimeters);
257        let c2 = ExtendedCode::Unit(Unit::Inches);
258        assert_code!(c1, "%MOMM*%\n");
259        assert_code!(c2, "%MOIN*%\n");
260    }
261
262    mod aperture_definition {
263        use super::*;
264
265        #[test]
266        fn test_circle_definition() {
267            let ad1 = ApertureDefinition {
268                code: 10,
269                aperture: Aperture::Circle(Circle {
270                    diameter: 4.0,
271                    hole_diameter: Some(2.0),
272                }),
273            };
274            let ad2 = ApertureDefinition {
275                code: 11,
276                aperture: Aperture::Circle(Circle {
277                    diameter: 4.5,
278                    hole_diameter: None,
279                }),
280            };
281            assert_partial_code!(ad1, "10C,4X2");
282            assert_partial_code!(ad2, "11C,4.5");
283        }
284
285        #[test]
286        fn test_rectangular_definition() {
287            let ad1 = ApertureDefinition {
288                code: 12,
289                aperture: Aperture::Rectangle(Rectangular {
290                    x: 1.5,
291                    y: 2.25,
292                    hole_diameter: Some(3.8),
293                }),
294            };
295            let ad2 = ApertureDefinition {
296                code: 13,
297                aperture: Aperture::Rectangle(Rectangular {
298                    x: 1.0,
299                    y: 1.0,
300                    hole_diameter: None,
301                }),
302            };
303            let ad3 = ApertureDefinition {
304                code: 14,
305                aperture: Aperture::Obround(Rectangular {
306                    x: 2.0,
307                    y: 4.5,
308                    hole_diameter: None,
309                }),
310            };
311            assert_partial_code!(ad1, "12R,1.5X2.25X3.8");
312            assert_partial_code!(ad2, "13R,1X1");
313            assert_partial_code!(ad3, "14O,2X4.5");
314        }
315
316        #[test]
317        fn test_polygon_definition() {
318            let ad1 = ApertureDefinition {
319                code: 15,
320                aperture: Aperture::Polygon(Polygon {
321                    diameter: 4.5,
322                    vertices: 3,
323                    rotation: None,
324                    hole_diameter: None,
325                }),
326            };
327            let ad2 = ApertureDefinition {
328                code: 16,
329                aperture: Aperture::Polygon(Polygon {
330                    diameter: 5.0,
331                    vertices: 4,
332                    rotation: Some(30.6),
333                    hole_diameter: None,
334                }),
335            };
336            let ad3 = ApertureDefinition {
337                code: 17,
338                aperture: Aperture::Polygon(Polygon {
339                    diameter: 5.5,
340                    vertices: 5,
341                    rotation: None,
342                    hole_diameter: Some(1.8),
343                }),
344            };
345            assert_partial_code!(ad1, "15P,4.5X3");
346            assert_partial_code!(ad2, "16P,5X4X30.6");
347            assert_partial_code!(ad3, "17P,5.5X5X0X1.8");
348        }
349
350        #[test]
351        fn test_macro_definition() {
352            let m1 = ApertureDefinition {
353                code: 42,
354                aperture: Aperture::Macro("NO_ARGS1".to_string(), None),
355            };
356            let m2 = ApertureDefinition {
357                code: 69,
358                aperture: Aperture::Macro(
359                    "With_Args2".to_string(),
360                    Some(vec![
361                        MacroDecimal::Variable(1),
362                        MacroDecimal::Value(0.25),
363                        MacroDecimal::Expression("$1x$2".to_string()),
364                    ]),
365                ),
366            };
367            assert_partial_code!(m1, "42NO_ARGS1");
368            assert_partial_code!(m2, "69With_Args2,$1X0.25X$1x$2");
369        }
370    }
371
372    #[test]
373    fn test_polarity() {
374        let d = ExtendedCode::LoadPolarity(Polarity::Dark);
375        let c = ExtendedCode::LoadPolarity(Polarity::Clear);
376        assert_code!(d, "%LPD*%\n");
377        assert_code!(c, "%LPC*%\n");
378    }
379
380    #[test]
381    fn test_step_and_repeat() {
382        let o = ExtendedCode::StepAndRepeat(StepAndRepeat::Open {
383            repeat_x: 2,
384            repeat_y: 3,
385            distance_x: 2.0,
386            distance_y: 3.0,
387        });
388        let c = ExtendedCode::StepAndRepeat(StepAndRepeat::Close);
389        assert_code!(o, "%SRX2Y3I2J3*%\n");
390        assert_code!(c, "%SR*%\n");
391    }
392
393    #[test]
394    fn test_aperture_block() {
395        let o = ExtendedCode::ApertureBlock(ApertureBlock::Open { code: 102 });
396        let c = ExtendedCode::ApertureBlock(ApertureBlock::Close);
397        assert_code!(o, "%AB102*%\n");
398        assert_code!(c, "%AB*%\n");
399    }
400
401    #[test]
402    fn test_delete_aperture_attribute() {
403        let c = ExtendedCode::DeleteAttribute(AttributeDeletionCriterion::SingleApertureAttribute(
404            "CustomAttribute".to_string(),
405        ));
406        assert_code!(c, "%TDCustomAttribute*%\n");
407        let s = ExtendedCode::DeleteAttribute(AttributeDeletionCriterion::SingleApertureAttribute(
408            ".AperFunction".to_string(),
409        ));
410        assert_code!(s, "%TD.AperFunction*%\n");
411    }
412
413    #[test]
414    fn test_delete_object_attribute() {
415        let c = ExtendedCode::DeleteAttribute(AttributeDeletionCriterion::SingleObjectAttribute(
416            "CustomAttribute".to_string(),
417        ));
418        assert_code!(c, "%TDCustomAttribute*%\n");
419        let c = ExtendedCode::DeleteAttribute(AttributeDeletionCriterion::SingleObjectAttribute(
420            ".N".to_string(),
421        ));
422        assert_code!(c, "%TD.N*%\n");
423    }
424
425    #[test]
426    fn test_delete_all_object_and_aperture_attributes() {
427        let d = ExtendedCode::DeleteAttribute(
428            AttributeDeletionCriterion::AllApertureAndObjectAttributes,
429        );
430        assert_code!(d, "%TD*%\n");
431    }
432
433    mod file_attribute {
434        use super::*;
435        #[test]
436        fn test_part() {
437            let part =
438                ExtendedCode::FileAttribute(FileAttribute::Part(Part::Other("Part 1".into())));
439            assert_code!(part, "%TF.Part,Other,Part 1*%\n");
440        }
441
442        #[test]
443        fn test_generation_software() {
444            let gensw1 = ExtendedCode::FileAttribute(FileAttribute::GenerationSoftware(
445                GenerationSoftware::new("Vendor 1", "App 1", None),
446            ));
447            assert_code!(gensw1, "%TF.GenerationSoftware,Vendor 1,App 1*%\n");
448
449            let gensw2 = ExtendedCode::FileAttribute(FileAttribute::GenerationSoftware(
450                GenerationSoftware::new("Vendor 1", "App 1", Some("1.2.3")),
451            ));
452            assert_code!(gensw2, "%TF.GenerationSoftware,Vendor 1,App 1,1.2.3*%\n");
453        }
454
455        #[test]
456        fn test_creation_date() {
457            let date = GerberDate::parse_from_rfc3339("2025-06-10T16:25:00+02:00").unwrap();
458            let date = ExtendedCode::FileAttribute(FileAttribute::CreationDate(date));
459            assert_code!(date, "%TF.CreationDate,2025-06-10T16:25:00+02:00*%\n");
460        }
461
462        #[test]
463        fn test_project_id() {
464            let proj = ExtendedCode::FileAttribute(FileAttribute::ProjectId {
465                id: "Project".into(),
466                uuid: Uuid::max(),
467                revision: "rev1".into(),
468            });
469            assert_code!(
470                proj,
471                "%TF.ProjectId,Project,ffffffff-ffff-ffff-ffff-ffffffffffff,rev1*%\n"
472            );
473        }
474
475        mod file_function {
476            use super::*;
477            #[test]
478            fn test_copper() {
479                let func = ExtendedCode::FileAttribute(FileAttribute::FileFunction(
480                    FileFunction::Copper {
481                        layer: 1,
482                        pos: ExtendedPosition::Top,
483                        copper_type: None,
484                    },
485                ));
486                assert_code!(func, "%TF.FileFunction,Copper,L1,Top*%\n");
487
488                let func = ExtendedCode::FileAttribute(FileAttribute::FileFunction(
489                    FileFunction::Copper {
490                        layer: 2,
491                        pos: ExtendedPosition::Bottom,
492                        copper_type: Some(CopperType::Hatched),
493                    },
494                ));
495                assert_code!(func, "%TF.FileFunction,Copper,L2,Bot,Hatched*%\n");
496
497                let func = ExtendedCode::FileAttribute(FileAttribute::FileFunction(
498                    FileFunction::Copper {
499                        layer: 3,
500                        pos: ExtendedPosition::Bottom,
501                        copper_type: Some(CopperType::Mixed),
502                    },
503                ));
504                assert_code!(func, "%TF.FileFunction,Copper,L3,Bot,Mixed*%\n");
505
506                let func = ExtendedCode::FileAttribute(FileAttribute::FileFunction(
507                    FileFunction::Copper {
508                        layer: 4,
509                        pos: ExtendedPosition::Bottom,
510                        copper_type: Some(CopperType::Plane),
511                    },
512                ));
513                assert_code!(func, "%TF.FileFunction,Copper,L4,Bot,Plane*%\n");
514
515                let func = ExtendedCode::FileAttribute(FileAttribute::FileFunction(
516                    FileFunction::Copper {
517                        layer: 5,
518                        pos: ExtendedPosition::Bottom,
519                        copper_type: Some(CopperType::Signal),
520                    },
521                ));
522                assert_code!(func, "%TF.FileFunction,Copper,L5,Bot,Signal*%\n");
523            }
524
525            #[test]
526            fn test_plated() {
527                let func = ExtendedCode::FileAttribute(FileAttribute::FileFunction(
528                    FileFunction::Plated {
529                        from_layer: 1,
530                        to_layer: 2,
531                        drill: PlatedDrill::Blind,
532                        label: None,
533                    },
534                ));
535                assert_code!(func, "%TF.FileFunction,Plated,1,2,Blind*%\n");
536
537                let func = ExtendedCode::FileAttribute(FileAttribute::FileFunction(
538                    FileFunction::Plated {
539                        from_layer: 1,
540                        to_layer: 4,
541                        drill: PlatedDrill::PlatedThroughHole,
542                        label: None,
543                    },
544                ));
545                assert_code!(func, "%TF.FileFunction,Plated,1,4,PTH*%\n");
546
547                let func = ExtendedCode::FileAttribute(FileAttribute::FileFunction(
548                    FileFunction::Plated {
549                        from_layer: 2,
550                        to_layer: 3,
551                        drill: PlatedDrill::Buried,
552                        label: None,
553                    },
554                ));
555                assert_code!(func, "%TF.FileFunction,Plated,2,3,Buried*%\n");
556
557                let func = ExtendedCode::FileAttribute(FileAttribute::FileFunction(
558                    FileFunction::Plated {
559                        from_layer: 1,
560                        to_layer: 2,
561                        drill: PlatedDrill::PlatedThroughHole,
562                        label: Some(DrillRouteType::Drill),
563                    },
564                ));
565                assert_code!(func, "%TF.FileFunction,Plated,1,2,PTH,Drill*%\n");
566
567                let func = ExtendedCode::FileAttribute(FileAttribute::FileFunction(
568                    FileFunction::Plated {
569                        from_layer: 1,
570                        to_layer: 2,
571                        drill: PlatedDrill::PlatedThroughHole,
572                        label: Some(DrillRouteType::Mixed),
573                    },
574                ));
575                assert_code!(func, "%TF.FileFunction,Plated,1,2,PTH,Mixed*%\n");
576
577                let func = ExtendedCode::FileAttribute(FileAttribute::FileFunction(
578                    FileFunction::Plated {
579                        from_layer: 1,
580                        to_layer: 2,
581                        drill: PlatedDrill::PlatedThroughHole,
582                        label: Some(DrillRouteType::Route),
583                    },
584                ));
585                assert_code!(func, "%TF.FileFunction,Plated,1,2,PTH,Rout*%\n");
586            }
587
588            #[test]
589            fn test_non_plated() {
590                let func = ExtendedCode::FileAttribute(FileAttribute::FileFunction(
591                    FileFunction::NonPlated {
592                        from_layer: 1,
593                        to_layer: 2,
594                        drill: NonPlatedDrill::Blind,
595                        label: None,
596                    },
597                ));
598                assert_code!(func, "%TF.FileFunction,NonPlated,1,2,Blind*%\n");
599
600                let func = ExtendedCode::FileAttribute(FileAttribute::FileFunction(
601                    FileFunction::NonPlated {
602                        from_layer: 1,
603                        to_layer: 4,
604                        drill: NonPlatedDrill::NonPlatedThroughHole,
605                        label: None,
606                    },
607                ));
608                assert_code!(func, "%TF.FileFunction,NonPlated,1,4,NPTH*%\n");
609
610                let func = ExtendedCode::FileAttribute(FileAttribute::FileFunction(
611                    FileFunction::NonPlated {
612                        from_layer: 2,
613                        to_layer: 3,
614                        drill: NonPlatedDrill::Buried,
615                        label: None,
616                    },
617                ));
618                assert_code!(func, "%TF.FileFunction,NonPlated,2,3,Buried*%\n");
619
620                let func = ExtendedCode::FileAttribute(FileAttribute::FileFunction(
621                    FileFunction::NonPlated {
622                        from_layer: 1,
623                        to_layer: 2,
624                        drill: NonPlatedDrill::NonPlatedThroughHole,
625                        label: Some(DrillRouteType::Drill),
626                    },
627                ));
628                assert_code!(func, "%TF.FileFunction,NonPlated,1,2,NPTH,Drill*%\n");
629
630                let func = ExtendedCode::FileAttribute(FileAttribute::FileFunction(
631                    FileFunction::NonPlated {
632                        from_layer: 1,
633                        to_layer: 2,
634                        drill: NonPlatedDrill::NonPlatedThroughHole,
635                        label: Some(DrillRouteType::Mixed),
636                    },
637                ));
638                assert_code!(func, "%TF.FileFunction,NonPlated,1,2,NPTH,Mixed*%\n");
639
640                let func = ExtendedCode::FileAttribute(FileAttribute::FileFunction(
641                    FileFunction::NonPlated {
642                        from_layer: 1,
643                        to_layer: 2,
644                        drill: NonPlatedDrill::NonPlatedThroughHole,
645                        label: Some(DrillRouteType::Route),
646                    },
647                ));
648                assert_code!(func, "%TF.FileFunction,NonPlated,1,2,NPTH,Rout*%\n");
649            }
650
651            #[test]
652            fn test_profile() {
653                let func = ExtendedCode::FileAttribute(FileAttribute::FileFunction(
654                    FileFunction::Profile(None),
655                ));
656                assert_code!(func, "%TF.FileFunction,Profile*%\n");
657
658                let func = ExtendedCode::FileAttribute(FileAttribute::FileFunction(
659                    FileFunction::Profile(Some(Profile::Plated)),
660                ));
661                assert_code!(func, "%TF.FileFunction,Profile,P*%\n");
662
663                let func = ExtendedCode::FileAttribute(FileAttribute::FileFunction(
664                    FileFunction::Profile(Some(Profile::NonPlated)),
665                ));
666                assert_code!(func, "%TF.FileFunction,Profile,NP*%\n");
667            }
668
669            #[test]
670            fn test_keepout() {
671                let func = ExtendedCode::FileAttribute(FileAttribute::FileFunction(
672                    FileFunction::KeepOut(Position::Top),
673                ));
674                assert_code!(func, "%TF.FileFunction,Keepout,Top*%\n");
675
676                let func = ExtendedCode::FileAttribute(FileAttribute::FileFunction(
677                    FileFunction::KeepOut(Position::Bottom),
678                ));
679                assert_code!(func, "%TF.FileFunction,Keepout,Bot*%\n");
680            }
681
682            macro_rules! test_position_and_index {
683                ($test:ident, $ff:ident, $value:literal) => {
684                    #[test]
685                    fn $test() {
686                        let func = ExtendedCode::FileAttribute(FileAttribute::FileFunction(
687                            FileFunction::$ff {
688                                pos: Position::Top,
689                                index: None,
690                            },
691                        ));
692                        assert_code!(func, &format!("%TF.FileFunction,{},Top*%\n", $value));
693
694                        let func = ExtendedCode::FileAttribute(FileAttribute::FileFunction(
695                            FileFunction::$ff {
696                                pos: Position::Top,
697                                index: Some(1),
698                            },
699                        ));
700                        assert_code!(func, &format!("%TF.FileFunction,{},Top,1*%\n", $value));
701
702                        let func = ExtendedCode::FileAttribute(FileAttribute::FileFunction(
703                            FileFunction::$ff {
704                                pos: Position::Bottom,
705                                index: None,
706                            },
707                        ));
708                        assert_code!(func, &format!("%TF.FileFunction,{},Bot*%\n", $value));
709                    }
710                };
711            }
712
713            macro_rules! test_layer_and_position {
714                ($test:ident, $ff:ident, $value:literal) => {
715                    #[test]
716                    fn $test() {
717                        let func = ExtendedCode::FileAttribute(FileAttribute::FileFunction(
718                            FileFunction::$ff {
719                                pos: Position::Top,
720                                layer: 1,
721                            },
722                        ));
723                        assert_code!(func, &format!("%TF.FileFunction,{},L{},Top*%\n", $value, 1));
724
725                        let func = ExtendedCode::FileAttribute(FileAttribute::FileFunction(
726                            FileFunction::$ff {
727                                pos: Position::Bottom,
728                                layer: 2,
729                            },
730                        ));
731                        assert_code!(func, &format!("%TF.FileFunction,{},L{},Bot*%\n", $value, 2));
732                    }
733                };
734            }
735
736            macro_rules! test_position {
737                ($test:ident, $ff:ident, $value:literal) => {
738                    #[test]
739                    fn $test() {
740                        let func = ExtendedCode::FileAttribute(FileAttribute::FileFunction(
741                            FileFunction::$ff(Position::Top),
742                        ));
743                        assert_code!(func, &format!("%TF.FileFunction,{},Top*%\n", $value));
744
745                        let func = ExtendedCode::FileAttribute(FileAttribute::FileFunction(
746                            FileFunction::$ff(Position::Bottom),
747                        ));
748                        assert_code!(func, &format!("%TF.FileFunction,{},Bot*%\n", $value));
749                    }
750                };
751            }
752
753            macro_rules! test_optional_position {
754                ($test:ident, $ff:ident, $value:literal) => {
755                    #[test]
756                    fn $test() {
757                        let func = ExtendedCode::FileAttribute(FileAttribute::FileFunction(
758                            FileFunction::$ff(None),
759                        ));
760                        assert_code!(func, &format!("%TF.FileFunction,{}*%\n", $value));
761
762                        let func = ExtendedCode::FileAttribute(FileAttribute::FileFunction(
763                            FileFunction::$ff(Some(Position::Top)),
764                        ));
765                        assert_code!(func, &format!("%TF.FileFunction,{},Top*%\n", $value));
766
767                        let func = ExtendedCode::FileAttribute(FileAttribute::FileFunction(
768                            FileFunction::$ff(Some(Position::Bottom)),
769                        ));
770                        assert_code!(func, &format!("%TF.FileFunction,{},Bot*%\n", $value));
771                    }
772                };
773            }
774            macro_rules! test_simple {
775                ($test:ident, $ff:ident, $value:literal) => {
776                    #[test]
777                    fn $test() {
778                        let func = ExtendedCode::FileAttribute(FileAttribute::FileFunction(
779                            FileFunction::$ff,
780                        ));
781                        assert_code!(func, &format!("%TF.FileFunction,{}*%\n", $value));
782                    }
783                };
784            }
785
786            macro_rules! test_string {
787                ($test:ident, $ff:ident, $value:literal) => {
788                    #[test]
789                    fn $test() {
790                        let string = "A String".to_string();
791                        let func = ExtendedCode::FileAttribute(FileAttribute::FileFunction(
792                            FileFunction::$ff(string.clone()),
793                        ));
794                        assert_code!(func, &format!("%TF.FileFunction,{},{}*%\n", $value, string));
795                    }
796                };
797            }
798
799            //
800            // It should be noted that the gerber spec is very-inconsistent with casing, e.g. "Soldermask" vs "AssemblyDrawing"
801            //
802
803            test_position_and_index!(test_soldermask, SolderMask, "Soldermask");
804            test_position_and_index!(test_legend, Legend, "Legend");
805            test_layer_and_position!(test_component, Component, "Component");
806            test_position!(test_paste, Paste, "Paste");
807            test_position!(test_glue, Glue, "Glue");
808            test_position_and_index!(test_carbonmask, CarbonMask, "Carbonmask");
809            test_position_and_index!(test_goldmask, GoldMask, "Goldmask");
810            test_position_and_index!(test_heatsinkmask, HeatsinkMask, "Heatsinkmask");
811            test_position_and_index!(test_peelablemask, PeelableMask, "Peelablemask");
812            test_position_and_index!(test_silvermask, SilverMask, "Silvermask");
813            test_position_and_index!(test_tinmask, TinMask, "Tinmask");
814            test_position!(test_depthroute, DepthRoute, "Depthrout");
815            test_optional_position!(test_vcut, VCut, "Vcut");
816            test_simple!(test_viafill, ViaFill, "Viafill");
817            test_position!(test_pads, Pads, "Pads");
818            test_string!(test_other, Other, "Other");
819            test_simple!(test_drillmap, DrillMap, "Drillmap");
820            test_simple!(
821                test_fabricationdrawing,
822                FabricationDrawing,
823                "FabricationDrawing"
824            );
825            test_simple!(test_vcutmap, VCutMap, "Vcutmap");
826            test_position!(test_assemblydrawing, AssemblyDrawing, "AssemblyDrawing");
827            test_simple!(test_arraydrawing, ArrayDrawing, "ArrayDrawing");
828            test_string!(test_otherdrawing, OtherDrawing, "OtherDrawing");
829        }
830
831        #[test]
832        fn test_polarity() {
833            let pol =
834                ExtendedCode::FileAttribute(FileAttribute::FilePolarity(FilePolarity::Positive));
835            assert_code!(pol, "%TF.FilePolarity,Positive*%\n");
836
837            let pol =
838                ExtendedCode::FileAttribute(FileAttribute::FilePolarity(FilePolarity::Negative));
839            assert_code!(pol, "%TF.FilePolarity,Negative*%\n");
840        }
841
842        #[test]
843        fn test_same_coordinates() {
844            let same_coordiantes =
845                ExtendedCode::FileAttribute(FileAttribute::SameCoordinates(None));
846            assert_code!(same_coordiantes, "%TF.SameCoordinates*%\n");
847
848            let same_coordiantes = ExtendedCode::FileAttribute(FileAttribute::SameCoordinates(
849                Some(Ident::Name("Name 1".to_string())),
850            ));
851            assert_code!(same_coordiantes, "%TF.SameCoordinates,Name 1*%\n");
852
853            let same_coordiantes = ExtendedCode::FileAttribute(FileAttribute::SameCoordinates(
854                Some(Ident::Uuid(Uuid::max())),
855            ));
856            assert_code!(
857                same_coordiantes,
858                "%TF.SameCoordinates,ffffffff-ffff-ffff-ffff-ffffffffffff*%\n"
859            );
860        }
861
862        #[test]
863        fn test_md5() {
864            let md5 = ExtendedCode::FileAttribute(FileAttribute::Md5("abcd1234".into()));
865            assert_code!(md5, "%TF.MD5,abcd1234*%\n");
866        }
867
868        mod user_defined_attribute {
869            use super::*;
870
871            #[test]
872            fn test_non_standard() {
873                let function = ExtendedCode::FileAttribute(FileAttribute::UserDefined {
874                    name: "NonStandardAttribute".to_string(),
875                    values: vec!["Value 1 ".to_string(), " Value 2".to_string()],
876                });
877                // NOTE there is no '.' prefix, spaces are not trimmed
878                assert_code!(function, "%TFNonStandardAttribute,Value 1 , Value 2*%\n");
879            }
880
881            #[test]
882            fn test_unsupported_standard() {
883                let function = ExtendedCode::FileAttribute(FileAttribute::UserDefined {
884                    name: ".UnsupportedStandardAttribute".to_string(),
885                    values: vec!["Value 1 ".to_string(), " Value 2".to_string()],
886                });
887                // NOTE there *is* a '.' prefix, spaces are not trimmed
888                assert_code!(
889                    function,
890                    "%TF.UnsupportedStandardAttribute,Value 1 , Value 2*%\n"
891                );
892            }
893        }
894    }
895
896    mod aperture_attribute {
897        use super::*;
898        //
899        // "Drill and rout layers"
900        //
901
902        #[test]
903        fn test_via_drill() {
904            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
905                ApertureFunction::ViaDrill(None),
906            ));
907            assert_code!(function, "%TA.AperFunction,ViaDrill*%\n");
908
909            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
910                ApertureFunction::ViaDrill(Some(IPC4761ViaProtection::None)),
911            ));
912            assert_code!(function, "%TA.AperFunction,ViaDrill,None*%\n");
913
914            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
915                ApertureFunction::ViaDrill(Some(IPC4761ViaProtection::Ia)),
916            ));
917            assert_code!(function, "%TA.AperFunction,ViaDrill,Ia*%\n");
918
919            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
920                ApertureFunction::ViaDrill(Some(IPC4761ViaProtection::Ib)),
921            ));
922            assert_code!(function, "%TA.AperFunction,ViaDrill,Ib*%\n");
923
924            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
925                ApertureFunction::ViaDrill(Some(IPC4761ViaProtection::IIa)),
926            ));
927            assert_code!(function, "%TA.AperFunction,ViaDrill,IIa*%\n");
928
929            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
930                ApertureFunction::ViaDrill(Some(IPC4761ViaProtection::IIb)),
931            ));
932            assert_code!(function, "%TA.AperFunction,ViaDrill,IIb*%\n");
933
934            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
935                ApertureFunction::ViaDrill(Some(IPC4761ViaProtection::IIIa)),
936            ));
937            assert_code!(function, "%TA.AperFunction,ViaDrill,IIIa*%\n");
938
939            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
940                ApertureFunction::ViaDrill(Some(IPC4761ViaProtection::IIIb)),
941            ));
942            assert_code!(function, "%TA.AperFunction,ViaDrill,IIIb*%\n");
943
944            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
945                ApertureFunction::ViaDrill(Some(IPC4761ViaProtection::IVa)),
946            ));
947            assert_code!(function, "%TA.AperFunction,ViaDrill,IVa*%\n");
948
949            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
950                ApertureFunction::ViaDrill(Some(IPC4761ViaProtection::IVb)),
951            ));
952            assert_code!(function, "%TA.AperFunction,ViaDrill,IVb*%\n");
953
954            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
955                ApertureFunction::ViaDrill(Some(IPC4761ViaProtection::V)),
956            ));
957            assert_code!(function, "%TA.AperFunction,ViaDrill,V*%\n");
958
959            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
960                ApertureFunction::ViaDrill(Some(IPC4761ViaProtection::VI)),
961            ));
962            assert_code!(function, "%TA.AperFunction,ViaDrill,VI*%\n");
963
964            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
965                ApertureFunction::ViaDrill(Some(IPC4761ViaProtection::VII)),
966            ));
967            assert_code!(function, "%TA.AperFunction,ViaDrill,VII*%\n");
968        }
969
970        #[test]
971        fn test_backdrill() {
972            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
973                ApertureFunction::BackDrill,
974            ));
975            assert_code!(function, "%TA.AperFunction,BackDrill*%\n");
976        }
977
978        #[test]
979        fn test_component_drill() {
980            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
981                ApertureFunction::ComponentDrill { function: None },
982            ));
983            assert_code!(function, "%TA.AperFunction,ComponentDrill*%\n");
984
985            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
986                ApertureFunction::ComponentDrill {
987                    function: Some(ComponentDrill::PressFit),
988                },
989            ));
990            assert_code!(function, "%TA.AperFunction,ComponentDrill,PressFit*%\n");
991        }
992
993        #[test]
994        fn test_mechanical_drill() {
995            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
996                ApertureFunction::MechanicalDrill { function: None },
997            ));
998            assert_code!(function, "%TA.AperFunction,MechanicalDrill*%\n");
999
1000            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
1001                ApertureFunction::MechanicalDrill {
1002                    function: Some(DrillFunction::Other),
1003                },
1004            ));
1005            assert_code!(function, "%TA.AperFunction,MechanicalDrill,Other*%\n");
1006
1007            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
1008                ApertureFunction::MechanicalDrill {
1009                    function: Some(DrillFunction::BreakOut),
1010                },
1011            ));
1012            assert_code!(function, "%TA.AperFunction,MechanicalDrill,Breakout*%\n");
1013
1014            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
1015                ApertureFunction::MechanicalDrill {
1016                    function: Some(DrillFunction::Tooling),
1017                },
1018            ));
1019            assert_code!(function, "%TA.AperFunction,MechanicalDrill,Tooling*%\n");
1020        }
1021
1022        #[test]
1023        fn test_catellated_drill() {
1024            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
1025                ApertureFunction::CastellatedDrill,
1026            ));
1027            assert_code!(function, "%TA.AperFunction,CastellatedDrill*%\n");
1028        }
1029
1030        #[test]
1031        fn test_other_drill() {
1032            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
1033                ApertureFunction::OtherDrill("CustomDrill".to_string()),
1034            ));
1035            assert_code!(function, "%TA.AperFunction,OtherDrill,CustomDrill*%\n");
1036        }
1037
1038        //
1039        // "Copper layers"
1040        //
1041        #[test]
1042        fn test_component_pad() {
1043            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
1044                ApertureFunction::ComponentPad,
1045            ));
1046            assert_code!(function, "%TA.AperFunction,ComponentPad*%\n");
1047        }
1048
1049        #[test]
1050        fn test_smd_pad() {
1051            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
1052                ApertureFunction::SmdPad(SmdPadType::CopperDefined),
1053            ));
1054            assert_code!(function, "%TA.AperFunction,SMDPad,CuDef*%\n");
1055
1056            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
1057                ApertureFunction::SmdPad(SmdPadType::SoldermaskDefined),
1058            ));
1059            assert_code!(function, "%TA.AperFunction,SMDPad,SMDef*%\n");
1060        }
1061
1062        #[test]
1063        fn test_bga_pad() {
1064            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
1065                ApertureFunction::BgaPad(SmdPadType::CopperDefined),
1066            ));
1067            assert_code!(function, "%TA.AperFunction,BGAPad,CuDef*%\n");
1068
1069            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
1070                ApertureFunction::BgaPad(SmdPadType::SoldermaskDefined),
1071            ));
1072            assert_code!(function, "%TA.AperFunction,BGAPad,SMDef*%\n");
1073        }
1074
1075        #[test]
1076        fn test_connector_pad() {
1077            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
1078                ApertureFunction::ConnectorPad,
1079            ));
1080            assert_code!(function, "%TA.AperFunction,ConnectorPad*%\n");
1081        }
1082
1083        #[test]
1084        fn test_headsink_pad() {
1085            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
1086                ApertureFunction::HeatsinkPad,
1087            ));
1088            assert_code!(function, "%TA.AperFunction,HeatsinkPad*%\n");
1089        }
1090
1091        #[test]
1092        fn test_via_pad() {
1093            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
1094                ApertureFunction::ViaPad,
1095            ));
1096            assert_code!(function, "%TA.AperFunction,ViaPad*%\n");
1097        }
1098
1099        #[test]
1100        fn test_test_pad() {
1101            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
1102                ApertureFunction::TestPad,
1103            ));
1104            assert_code!(function, "%TA.AperFunction,TestPad*%\n");
1105        }
1106
1107        #[test]
1108        fn test_castellated_pad() {
1109            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
1110                ApertureFunction::CastellatedPad,
1111            ));
1112            assert_code!(function, "%TA.AperFunction,CastellatedPad*%\n");
1113        }
1114
1115        #[test]
1116        fn test_fiducial_pad() {
1117            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
1118                ApertureFunction::FiducialPad(FiducialScope::Global),
1119            ));
1120            assert_code!(function, "%TA.AperFunction,FiducialPad,Global*%\n");
1121
1122            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
1123                ApertureFunction::FiducialPad(FiducialScope::Local),
1124            ));
1125            assert_code!(function, "%TA.AperFunction,FiducialPad,Local*%\n");
1126
1127            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
1128                ApertureFunction::FiducialPad(FiducialScope::Panel),
1129            ));
1130            assert_code!(function, "%TA.AperFunction,FiducialPad,Panel*%\n");
1131        }
1132
1133        #[test]
1134        fn test_thermal_relief_pad() {
1135            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
1136                ApertureFunction::ThermalReliefPad,
1137            ));
1138            assert_code!(function, "%TA.AperFunction,ThermalReliefPad*%\n");
1139        }
1140
1141        #[test]
1142        fn test_washer_pad() {
1143            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
1144                ApertureFunction::WasherPad,
1145            ));
1146            assert_code!(function, "%TA.AperFunction,WasherPad*%\n");
1147        }
1148
1149        #[test]
1150        fn test_anti_pad() {
1151            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
1152                ApertureFunction::AntiPad,
1153            ));
1154            assert_code!(function, "%TA.AperFunction,AntiPad*%\n");
1155        }
1156
1157        #[test]
1158        fn test_other_pad() {
1159            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
1160                ApertureFunction::OtherPad("CustomPad".to_string()),
1161            ));
1162            assert_code!(function, "%TA.AperFunction,OtherPad,CustomPad*%\n");
1163        }
1164
1165        #[test]
1166        fn test_conductor() {
1167            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
1168                ApertureFunction::Conductor,
1169            ));
1170            assert_code!(function, "%TA.AperFunction,Conductor*%\n");
1171        }
1172
1173        #[test]
1174        fn test_etched_component() {
1175            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
1176                ApertureFunction::EtchedComponent,
1177            ));
1178            assert_code!(function, "%TA.AperFunction,EtchedComponent*%\n");
1179        }
1180
1181        #[test]
1182        fn test_non_conductor() {
1183            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
1184                ApertureFunction::NonConductor,
1185            ));
1186            assert_code!(function, "%TA.AperFunction,NonConductor*%\n");
1187        }
1188
1189        #[test]
1190        fn test_copper_balancing() {
1191            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
1192                ApertureFunction::CopperBalancing,
1193            ));
1194            assert_code!(function, "%TA.AperFunction,CopperBalancing*%\n");
1195        }
1196
1197        #[test]
1198        fn test_border() {
1199            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
1200                ApertureFunction::Border,
1201            ));
1202            assert_code!(function, "%TA.AperFunction,Border*%\n");
1203        }
1204
1205        #[test]
1206        fn test_other_copper() {
1207            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
1208                ApertureFunction::OtherCopper("CustomCopper".to_string()),
1209            ));
1210            assert_code!(function, "%TA.AperFunction,OtherCopper,CustomCopper*%\n");
1211        }
1212
1213        //
1214        // "All data layers"
1215        //
1216
1217        #[test]
1218        fn test_profile() {
1219            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
1220                ApertureFunction::Profile,
1221            ));
1222            assert_code!(function, "%TA.AperFunction,Profile*%\n");
1223        }
1224
1225        #[test]
1226        fn test_material() {
1227            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
1228                ApertureFunction::Material,
1229            ));
1230            assert_code!(function, "%TA.AperFunction,Material*%\n");
1231        }
1232
1233        #[test]
1234        fn test_non_material() {
1235            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
1236                ApertureFunction::NonMaterial,
1237            ));
1238            assert_code!(function, "%TA.AperFunction,NonMaterial*%\n");
1239        }
1240
1241        #[test]
1242        fn test_other() {
1243            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
1244                ApertureFunction::Other("CustomFunction".to_string()),
1245            ));
1246            assert_code!(function, "%TA.AperFunction,Other,CustomFunction*%\n");
1247        }
1248
1249        //
1250        // "Component layers"
1251        //
1252
1253        #[test]
1254        fn test_component_main() {
1255            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
1256                ApertureFunction::ComponentMain,
1257            ));
1258            assert_code!(function, "%TA.AperFunction,ComponentMain*%\n");
1259        }
1260
1261        #[test]
1262        fn test_component_outline() {
1263            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
1264                ApertureFunction::ComponentOutline(ComponentOutline::Body),
1265            ));
1266            assert_code!(function, "%TA.AperFunction,ComponentOutline,Body*%\n");
1267
1268            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
1269                ApertureFunction::ComponentOutline(ComponentOutline::Lead2Lead),
1270            ));
1271            assert_code!(function, "%TA.AperFunction,ComponentOutline,Lead2Lead*%\n");
1272
1273            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
1274                ApertureFunction::ComponentOutline(ComponentOutline::Footprint),
1275            ));
1276            assert_code!(function, "%TA.AperFunction,ComponentOutline,Footprint*%\n");
1277
1278            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
1279                ApertureFunction::ComponentOutline(ComponentOutline::Courtyard),
1280            ));
1281            assert_code!(function, "%TA.AperFunction,ComponentOutline,Courtyard*%\n");
1282        }
1283
1284        #[test]
1285        fn test_component_pin() {
1286            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
1287                ApertureFunction::ComponentPin,
1288            ));
1289            assert_code!(function, "%TA.AperFunction,ComponentPin*%\n");
1290        }
1291
1292        //
1293        // "2024.05 - 8.4 - Deprecated attribute values"
1294        //
1295
1296        #[test]
1297        fn test_slot_deprecated() {
1298            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
1299                ApertureFunction::Slot,
1300            ));
1301            assert_code!(function, "%TA.AperFunction,Slot*%\n");
1302        }
1303        #[test]
1304        fn test_cutout_deprecated() {
1305            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
1306                ApertureFunction::CutOut,
1307            ));
1308            assert_code!(function, "%TA.AperFunction,CutOut*%\n");
1309        }
1310
1311        #[test]
1312        fn test_cavity_deprecated() {
1313            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
1314                ApertureFunction::Cavity,
1315            ));
1316            assert_code!(function, "%TA.AperFunction,Cavity*%\n");
1317        }
1318
1319        #[test]
1320        fn test_drawing_deprecated() {
1321            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::ApertureFunction(
1322                ApertureFunction::Drawing,
1323            ));
1324            assert_code!(function, "%TA.AperFunction,Drawing*%\n");
1325        }
1326
1327        mod user_defined_attribute {
1328            use super::*;
1329
1330            #[test]
1331            fn test_non_standard() {
1332                let function = ExtendedCode::ApertureAttribute(ApertureAttribute::UserDefined {
1333                    name: "NonStandardAttribute".to_string(),
1334                    values: vec!["Value 1 ".to_string(), " Value 2".to_string()],
1335                });
1336                // NOTE there is no '.' prefix, spaces are not trimmed
1337                assert_code!(function, "%TANonStandardAttribute,Value 1 , Value 2*%\n");
1338            }
1339
1340            #[test]
1341            fn test_unsupported_standard() {
1342                let function = ExtendedCode::ApertureAttribute(ApertureAttribute::UserDefined {
1343                    name: ".UnsupportedStandardAttribute".to_string(),
1344                    values: vec!["Value 1 ".to_string(), " Value 2".to_string()],
1345                });
1346                // NOTE there *is* a '.' prefix, spaces are not trimmed
1347                assert_code!(
1348                    function,
1349                    "%TA.UnsupportedStandardAttribute,Value 1 , Value 2*%\n"
1350                );
1351            }
1352        }
1353    }
1354
1355    mod drill_tolerance {
1356        use super::*;
1357
1358        #[test]
1359        fn test_attribute() {
1360            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::DrillTolerance {
1361                plus: 1.0,
1362                minus: 2.0,
1363            });
1364            assert_code!(function, "%TA.DrillTolerance,1,2*%\n");
1365        }
1366    }
1367
1368    mod flash_text {
1369        use super::*;
1370        #[test]
1371        fn test_attribute() {
1372            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::FlashText {
1373                text: "Test".to_string(),
1374                mode: TextMode::Characters,
1375                mirroring: Some(TextMirroring::Readable),
1376                font: Some("Font Name".to_string()),
1377                size: Some(10),
1378                comment: Some("A Comment".to_string()),
1379            });
1380            assert_code!(
1381                function,
1382                "%TA.FlashText,Test,C,R,Font Name,10,A Comment*%\n"
1383            );
1384
1385            let function = ExtendedCode::ApertureAttribute(ApertureAttribute::FlashText {
1386                text: "Test".to_string(),
1387                mode: TextMode::BarCode,
1388                mirroring: Some(TextMirroring::Mirrored),
1389                font: None,
1390                size: None,
1391                comment: None,
1392            });
1393            // 2024.05 - 5.6.12 .FlashText - "An empty field means that the corresponding meta-data is not specified."
1394            assert_code!(function, "%TA.FlashText,Test,B,M,,,*%\n");
1395        }
1396    }
1397
1398    mod object_attribute {
1399        use super::*;
1400
1401        mod net {
1402            use super::*;
1403            #[test]
1404            fn test_none() {
1405                let function = ExtendedCode::ObjectAttribute(ObjectAttribute::Net(Net::None));
1406                assert_code!(function, "%TO.N,*%\n");
1407            }
1408
1409            #[test]
1410            fn test_not_connected() {
1411                let function =
1412                    ExtendedCode::ObjectAttribute(ObjectAttribute::Net(Net::NotConnected));
1413                assert_code!(function, "%TO.N,N/C*%\n");
1414            }
1415
1416            #[test]
1417            fn test_connected() {
1418                let function =
1419                    ExtendedCode::ObjectAttribute(ObjectAttribute::Net(Net::Connected(vec![
1420                        "Net1".to_string(),
1421                        "Net2".to_string(),
1422                        "Net3".to_string(),
1423                    ])));
1424                assert_code!(function, "%TO.N,Net1,Net2,Net3*%\n");
1425            }
1426        }
1427
1428        mod pin {
1429            use super::*;
1430
1431            #[test]
1432            fn test_pin() {
1433                let function = ExtendedCode::ObjectAttribute(ObjectAttribute::Pin(Pin {
1434                    refdes: "U1".to_string(),
1435                    name: "1".to_string(),
1436                    function: None,
1437                }));
1438                assert_code!(function, "%TO.P,U1,1*%\n");
1439            }
1440
1441            #[test]
1442            fn test_pin_with_functin() {
1443                let function = ExtendedCode::ObjectAttribute(ObjectAttribute::Pin(Pin {
1444                    refdes: "Q1".to_string(),
1445                    name: "EP".to_string(),
1446                    function: Some("Thermal pad".to_string()),
1447                }));
1448                assert_code!(function, "%TO.P,Q1,EP,Thermal pad*%\n");
1449            }
1450        }
1451
1452        mod ref_des {
1453            use super::*;
1454
1455            #[test]
1456            fn test_component() {
1457                let function =
1458                    ExtendedCode::ObjectAttribute(ObjectAttribute::Component("R1".to_string()));
1459                assert_code!(function, "%TO.C,R1*%\n");
1460            }
1461        }
1462
1463        mod component_characteristics {
1464            use super::*;
1465
1466            macro_rules! test_string {
1467                ($test:ident, $cc:ident, $value:literal) => {
1468                    #[test]
1469                    fn $test() {
1470                        let string = "A String".to_string();
1471                        let func = ExtendedCode::ObjectAttribute(
1472                            ObjectAttribute::ComponentCharacteristics(
1473                                ComponentCharacteristics::$cc(string.clone()),
1474                            ),
1475                        );
1476                        assert_code!(func, &format!("%TO.{},{}*%\n", $value, string));
1477                    }
1478                };
1479            }
1480
1481            macro_rules! test_decimal {
1482                ($test:ident, $cc:ident, $value:literal) => {
1483                    #[test]
1484                    fn $test() {
1485                        let decimal = 100.00;
1486                        let func = ExtendedCode::ObjectAttribute(
1487                            ObjectAttribute::ComponentCharacteristics(
1488                                ComponentCharacteristics::$cc(decimal),
1489                            ),
1490                        );
1491                        assert_code!(func, &format!("%TO.{},{}*%\n", $value, decimal));
1492                    }
1493                };
1494            }
1495
1496            test_decimal!(test_rotation, Rotation, "CRot");
1497            test_string!(test_manufacturer, Manufacturer, "CMfr");
1498            test_string!(test_mpn, MPN, "CMPN");
1499            test_string!(test_val, Value, "CVal");
1500
1501            mod mount_type {
1502                use super::*;
1503
1504                #[test]
1505                fn test_through_hole() {
1506                    let function =
1507                        ExtendedCode::ObjectAttribute(ObjectAttribute::ComponentCharacteristics(
1508                            ComponentCharacteristics::Mount(ComponentMounting::ThroughHole),
1509                        ));
1510                    assert_code!(function, "%TO.CMnt,TH*%\n");
1511                }
1512
1513                #[test]
1514                fn test_smd() {
1515                    let function =
1516                        ExtendedCode::ObjectAttribute(ObjectAttribute::ComponentCharacteristics(
1517                            ComponentCharacteristics::Mount(ComponentMounting::SMD),
1518                        ));
1519                    assert_code!(function, "%TO.CMnt,SMD*%\n");
1520                }
1521
1522                #[test]
1523                fn test_press_fit() {
1524                    let function =
1525                        ExtendedCode::ObjectAttribute(ObjectAttribute::ComponentCharacteristics(
1526                            ComponentCharacteristics::Mount(ComponentMounting::PressFit),
1527                        ));
1528                    assert_code!(function, "%TO.CMnt,Pressfit*%\n");
1529                }
1530
1531                #[test]
1532                fn test_press_other() {
1533                    let function =
1534                        ExtendedCode::ObjectAttribute(ObjectAttribute::ComponentCharacteristics(
1535                            ComponentCharacteristics::Mount(ComponentMounting::Other),
1536                        ));
1537                    assert_code!(function, "%TO.CMnt,Other*%\n");
1538                }
1539            }
1540
1541            test_string!(test_footprint, Footprint, "CFtp");
1542            test_string!(test_packagename, PackageName, "CPgN");
1543            test_string!(test_packagedescription, PackageDescription, "CPgD");
1544            test_decimal!(test_height, Height, "CHgt");
1545            test_string!(test_libraryname, LibraryName, "CLbN");
1546            test_string!(test_librarydescription, LibraryDescription, "CLbD");
1547
1548            #[test]
1549            fn test_supplier() {
1550                let function =
1551                    ExtendedCode::ObjectAttribute(ObjectAttribute::ComponentCharacteristics(
1552                        ComponentCharacteristics::Supplier(vec![
1553                            SupplierPart {
1554                                supplier_name: "Supplier Name 1".to_string(),
1555                                supplier_part_reference: "Reference 1".to_string(),
1556                            },
1557                            SupplierPart {
1558                                supplier_name: " Supplier Name 2 ".to_string(),
1559                                supplier_part_reference: "Reference 2".to_string(),
1560                            },
1561                        ]),
1562                    ));
1563                // NOTE spaces are not trimmed
1564                assert_code!(
1565                    function,
1566                    "%TO.CSup,Supplier Name 1,Reference 1, Supplier Name 2 ,Reference 2*%\n"
1567                );
1568            }
1569        }
1570
1571        mod user_defined_attribute {
1572            use super::*;
1573
1574            #[test]
1575            fn test_non_standard() {
1576                let function = ExtendedCode::ObjectAttribute(ObjectAttribute::UserDefined {
1577                    name: "NonStandardAttribute".to_string(),
1578                    values: vec!["Value 1 ".to_string(), " Value 2".to_string()],
1579                });
1580                // NOTE there is no '.' prefix, spaces are not trimmed
1581                assert_code!(function, "%TONonStandardAttribute,Value 1 , Value 2*%\n");
1582            }
1583
1584            #[test]
1585            fn test_unsupported_standard() {
1586                let function = ExtendedCode::ObjectAttribute(ObjectAttribute::UserDefined {
1587                    name: ".UnsupportedStandardAttribute".to_string(),
1588                    values: vec!["Value 1 ".to_string(), " Value 2".to_string()],
1589                });
1590                // NOTE there *is* a '.' prefix, spaces are not trimmed
1591                assert_code!(
1592                    function,
1593                    "%TO.UnsupportedStandardAttribute,Value 1 , Value 2*%\n"
1594                );
1595            }
1596        }
1597    }
1598
1599    #[test]
1600    fn test_mirror_image() {
1601        let value = ExtendedCode::MirrorImage(ImageMirroring::None);
1602        assert_code!(value, "%MI*%\n");
1603        let value = ExtendedCode::MirrorImage(ImageMirroring::A);
1604        assert_code!(value, "%MIA1*%\n");
1605        let value = ExtendedCode::MirrorImage(ImageMirroring::B);
1606        assert_code!(value, "%MIB1*%\n");
1607        let value = ExtendedCode::MirrorImage(ImageMirroring::AB);
1608        assert_code!(value, "%MIA1B1*%\n");
1609    }
1610
1611    #[test]
1612    fn test_offset_image() {
1613        let value = ExtendedCode::OffsetImage(ImageOffset { a: 0.0, b: 0.0 });
1614        assert_code!(value, "%OF*%\n");
1615        let value = ExtendedCode::OffsetImage(ImageOffset {
1616            a: 99999.99999,
1617            b: 0.0,
1618        });
1619        assert_code!(value, "%OFA99999.99999*%\n");
1620        let value = ExtendedCode::OffsetImage(ImageOffset {
1621            a: 0.0,
1622            b: 99999.99999,
1623        });
1624        assert_code!(value, "%OFB99999.99999*%\n");
1625        let value = ExtendedCode::OffsetImage(ImageOffset {
1626            a: -99999.99999,
1627            b: -99999.99999,
1628        });
1629        assert_code!(value, "%OFA-99999.99999B-99999.99999*%\n");
1630    }
1631
1632    #[test]
1633    fn test_scale_image() {
1634        let value = ExtendedCode::ScaleImage(ImageScaling { a: 0.0, b: 0.0 });
1635        assert_code!(value, "%SF*%\n");
1636        let value = ExtendedCode::ScaleImage(ImageScaling {
1637            a: 999.99999,
1638            b: 0.0,
1639        });
1640        assert_code!(value, "%SFA999.99999*%\n");
1641        let value = ExtendedCode::ScaleImage(ImageScaling {
1642            a: 0.0,
1643            b: 999.99999,
1644        });
1645        assert_code!(value, "%SFB999.99999*%\n");
1646        let value = ExtendedCode::ScaleImage(ImageScaling {
1647            a: -999.99999,
1648            b: -999.99999,
1649        });
1650        assert_code!(value, "%SFA-999.99999B-999.99999*%\n");
1651    }
1652
1653    #[test]
1654    fn test_rotate_image() {
1655        let value = ExtendedCode::RotateImage(ImageRotation::None);
1656        assert_code!(value, "%IR0*%\n");
1657        let value = ExtendedCode::RotateImage(ImageRotation::CCW_90);
1658        assert_code!(value, "%IR90*%\n");
1659        let value = ExtendedCode::RotateImage(ImageRotation::CCW_180);
1660        assert_code!(value, "%IR180*%\n");
1661        let value = ExtendedCode::RotateImage(ImageRotation::CCW_270);
1662        assert_code!(value, "%IR270*%\n");
1663    }
1664
1665    #[test]
1666    fn test_image_polarity() {
1667        let value = ExtendedCode::ImagePolarity(ImagePolarity::Positive);
1668        assert_code!(value, "%IPPOS*%\n");
1669        let value = ExtendedCode::ImagePolarity(ImagePolarity::Negative);
1670        assert_code!(value, "%IPNEG*%\n");
1671    }
1672
1673    #[test]
1674    fn test_axis_select() {
1675        let value = ExtendedCode::AxisSelect(AxisSelect::AXBY);
1676        assert_code!(value, "%ASAXBY*%\n");
1677        let value = ExtendedCode::AxisSelect(AxisSelect::AYBX);
1678        assert_code!(value, "%ASAYBX*%\n");
1679    }
1680
1681    #[test]
1682    fn test_image_name() {
1683        let value = ExtendedCode::ImageName(ImageName {
1684            name: "PANEL_1".to_string(),
1685        });
1686        assert_code!(value, "%INPANEL_1*%\n");
1687    }
1688}