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
25mod attributes;
26mod codegen;
27mod coordinates;
28mod errors;
29mod extended_codes;
30mod function_codes;
31mod macros;
32mod traits;
33mod types;
34
35pub use crate::attributes::*;
36pub use crate::codegen::*;
37pub use crate::coordinates::*;
38pub use crate::errors::*;
39pub use crate::extended_codes::*;
40pub use crate::function_codes::*;
41pub use crate::macros::*;
42pub use crate::traits::GerberCode;
43pub use crate::types::*;
44
45#[cfg(test)]
46mod test {
47    use std::io::BufWriter;
48
49    use super::traits::PartialGerberCode;
50    use super::*;
51
52    #[test]
53    fn test_serialize() {
54        //! The serialize method of the GerberCode trait should generate strings.
55        let comment = GCode::Comment("testcomment".to_string());
56        assert_code!(comment, "G04 testcomment*\n");
57    }
58
59    #[test]
60    fn test_vec_serialize() {
61        //! A `Vec<T: GerberCode>` should also implement `GerberCode`.
62        let mut v = Vec::new();
63        v.push(GCode::Comment("comment 1".to_string()));
64        v.push(GCode::Comment("another one".to_string()));
65        assert_code!(v, "G04 comment 1*\nG04 another one*\n");
66    }
67
68    #[test]
69    fn test_command_serialize() {
70        //! A `Command` should implement `GerberCode`
71        let c = Command::FunctionCode(FunctionCode::GCode(GCode::Comment("comment".to_string())));
72        assert_code!(c, "G04 comment*\n");
73    }
74
75    #[test]
76    fn test_interpolation_mode() {
77        let mut commands = Vec::new();
78        let c1 = GCode::InterpolationMode(InterpolationMode::Linear);
79        let c2 = GCode::InterpolationMode(InterpolationMode::ClockwiseCircular);
80        let c3 = GCode::InterpolationMode(InterpolationMode::CounterclockwiseCircular);
81        commands.push(c1);
82        commands.push(c2);
83        commands.push(c3);
84        assert_code!(commands, "G01*\nG02*\nG03*\n");
85    }
86
87    #[test]
88    fn test_region_mode() {
89        let mut commands = Vec::new();
90        commands.push(GCode::RegionMode(true));
91        commands.push(GCode::RegionMode(false));
92        assert_code!(commands, "G36*\nG37*\n");
93    }
94
95    #[test]
96    fn test_quadrant_mode() {
97        let mut commands = Vec::new();
98        commands.push(GCode::QuadrantMode(QuadrantMode::Single));
99        commands.push(GCode::QuadrantMode(QuadrantMode::Multi));
100        assert_code!(commands, "G74*\nG75*\n");
101    }
102
103    #[test]
104    fn test_end_of_file() {
105        let c = MCode::EndOfFile;
106        assert_code!(c, "M02*\n");
107    }
108
109    #[test]
110    fn test_operation_interpolate() {
111        let cf = CoordinateFormat::new(2, 5);
112        let c1 = Operation::Interpolate(
113            Coordinates::new(1, 2, cf),
114            Some(CoordinateOffset::new(5, 10, cf)),
115        );
116        assert_code!(c1, "X100000Y200000I500000J1000000D01*\n");
117        let c2 = Operation::Interpolate(Coordinates::at_y(-2, CoordinateFormat::new(4, 4)), None);
118        assert_code!(c2, "Y-20000D01*\n");
119        let cf = CoordinateFormat::new(4, 4);
120        let c3 = Operation::Interpolate(
121            Coordinates::at_x(1, cf),
122            Some(CoordinateOffset::at_y(2, cf)),
123        );
124        assert_code!(c3, "X10000J20000D01*\n");
125    }
126
127    #[test]
128    fn test_operation_move() {
129        let c = Operation::Move(Coordinates::new(23, 42, CoordinateFormat::new(6, 4)));
130        assert_code!(c, "X230000Y420000D02*\n");
131    }
132
133    #[test]
134    fn test_operation_flash() {
135        let c = Operation::Flash(Coordinates::new(23, 42, CoordinateFormat::new(4, 4)));
136        assert_code!(c, "X230000Y420000D03*\n");
137    }
138
139    #[test]
140    fn test_select_aperture() {
141        let c1 = DCode::SelectAperture(10);
142        assert_code!(c1, "D10*\n");
143        let c2 = DCode::SelectAperture(2147483647);
144        assert_code!(c2, "D2147483647*\n");
145    }
146
147    #[test]
148    fn test_coordinate_format() {
149        let c = ExtendedCode::CoordinateFormat(CoordinateFormat::new(2, 5));
150        assert_code!(c, "%FSLAX25Y25*%\n");
151    }
152
153    #[test]
154    fn test_unit() {
155        let c1 = ExtendedCode::Unit(Unit::Millimeters);
156        let c2 = ExtendedCode::Unit(Unit::Inches);
157        assert_code!(c1, "%MOMM*%\n");
158        assert_code!(c2, "%MOIN*%\n");
159    }
160
161    #[test]
162    fn test_aperture_circle_definition() {
163        let ad1 = ApertureDefinition {
164            code: 10,
165            aperture: Aperture::Circle(Circle {
166                diameter: 4.0,
167                hole_diameter: Some(2.0),
168            }),
169        };
170        let ad2 = ApertureDefinition {
171            code: 11,
172            aperture: Aperture::Circle(Circle {
173                diameter: 4.5,
174                hole_diameter: None,
175            }),
176        };
177        assert_partial_code!(ad1, "10C,4X2");
178        assert_partial_code!(ad2, "11C,4.5");
179    }
180
181    #[test]
182    fn test_aperture_rectangular_definition() {
183        let ad1 = ApertureDefinition {
184            code: 12,
185            aperture: Aperture::Rectangle(Rectangular {
186                x: 1.5,
187                y: 2.25,
188                hole_diameter: Some(3.8),
189            }),
190        };
191        let ad2 = ApertureDefinition {
192            code: 13,
193            aperture: Aperture::Rectangle(Rectangular {
194                x: 1.0,
195                y: 1.0,
196                hole_diameter: None,
197            }),
198        };
199        let ad3 = ApertureDefinition {
200            code: 14,
201            aperture: Aperture::Obround(Rectangular {
202                x: 2.0,
203                y: 4.5,
204                hole_diameter: None,
205            }),
206        };
207        assert_partial_code!(ad1, "12R,1.5X2.25X3.8");
208        assert_partial_code!(ad2, "13R,1X1");
209        assert_partial_code!(ad3, "14O,2X4.5");
210    }
211
212    #[test]
213    fn test_aperture_polygon_definition() {
214        let ad1 = ApertureDefinition {
215            code: 15,
216            aperture: Aperture::Polygon(Polygon {
217                diameter: 4.5,
218                vertices: 3,
219                rotation: None,
220                hole_diameter: None,
221            }),
222        };
223        let ad2 = ApertureDefinition {
224            code: 16,
225            aperture: Aperture::Polygon(Polygon {
226                diameter: 5.0,
227                vertices: 4,
228                rotation: Some(30.6),
229                hole_diameter: None,
230            }),
231        };
232        let ad3 = ApertureDefinition {
233            code: 17,
234            aperture: Aperture::Polygon(Polygon {
235                diameter: 5.5,
236                vertices: 5,
237                rotation: None,
238                hole_diameter: Some(1.8),
239            }),
240        };
241        assert_partial_code!(ad1, "15P,4.5X3");
242        assert_partial_code!(ad2, "16P,5X4X30.6");
243        assert_partial_code!(ad3, "17P,5.5X5X0X1.8");
244    }
245
246    #[test]
247    fn test_polarity_serialize() {
248        let d = ExtendedCode::LoadPolarity(Polarity::Dark);
249        let c = ExtendedCode::LoadPolarity(Polarity::Clear);
250        assert_code!(d, "%LPD*%\n");
251        assert_code!(c, "%LPC*%\n");
252    }
253
254    #[test]
255    fn test_step_and_repeat_serialize() {
256        let o = ExtendedCode::StepAndRepeat(StepAndRepeat::Open {
257            repeat_x: 2,
258            repeat_y: 3,
259            distance_x: 2.0,
260            distance_y: 3.0,
261        });
262        let c = ExtendedCode::StepAndRepeat(StepAndRepeat::Close);
263        assert_code!(o, "%SRX2Y3I2J3*%\n");
264        assert_code!(c, "%SR*%\n");
265    }
266
267    #[test]
268    fn test_delete_attribute_serialize() {
269        let d = ExtendedCode::DeleteAttribute("foo".into());
270        assert_code!(d, "%TDfoo*%\n");
271    }
272
273    #[test]
274    fn test_file_attribute_serialize() {
275        let part = ExtendedCode::FileAttribute(FileAttribute::Part(Part::Other("foo".into())));
276        assert_code!(part, "%TF.Part,Other,foo*%\n");
277
278        let gensw1 = ExtendedCode::FileAttribute(FileAttribute::GenerationSoftware(
279            GenerationSoftware::new("Vend0r", "superpcb", None),
280        ));
281        assert_code!(gensw1, "%TF.GenerationSoftware,Vend0r,superpcb*%\n");
282
283        let gensw2 = ExtendedCode::FileAttribute(FileAttribute::GenerationSoftware(
284            GenerationSoftware::new("Vend0r", "superpcb", Some("1.2.3")),
285        ));
286        assert_code!(gensw2, "%TF.GenerationSoftware,Vend0r,superpcb,1.2.3*%\n");
287    }
288}