1use std::convert::From;
4use std::io::Write;
5
6use crate::errors::{GerberError, GerberResult};
7use crate::traits::PartialGerberCode;
8
9#[derive(Debug, Clone, PartialEq)]
10pub struct ApertureMacro {
11 pub name: String,
12 pub content: Vec<MacroContent>,
13}
14
15impl ApertureMacro {
16 pub fn new<S: Into<String>>(name: S) -> Self {
17 ApertureMacro {
18 name: name.into(),
19 content: Vec::new(),
20 }
21 }
22
23 pub fn add_content<C>(mut self, c: C) -> Self
24 where
25 C: Into<MacroContent>,
26 {
27 self.content.push(c.into());
28 self
29 }
30
31 pub fn add_content_mut<C>(&mut self, c: C)
32 where
33 C: Into<MacroContent>,
34 {
35 self.content.push(c.into());
36 }
37}
38
39impl<W: Write> PartialGerberCode<W> for ApertureMacro {
40 fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
41 if self.content.is_empty() {
42 return Err(GerberError::MissingDataError(
43 "There must be at least 1 content element in an aperture macro".into(),
44 ));
45 }
46 writeln!(writer, "AM{}*", self.name)?;
47 let mut first = true;
48 for content in &self.content {
49 if first {
50 first = false;
51 } else {
52 writeln!(writer)?;
53 }
54 content.serialize_partial(writer)?;
55 }
56 Ok(())
57 }
58}
59
60#[derive(Debug, Clone, PartialEq)]
61pub enum MacroDecimal {
63 Value(f64),
65 Variable(u32),
67 Expression(String),
74}
75
76impl MacroDecimal {
77 fn is_negative(&self) -> bool {
78 match *self {
79 MacroDecimal::Value(v) => v < 0.0,
80 MacroDecimal::Variable(_) => false,
81 MacroDecimal::Expression(_) => false,
82 }
83 }
84}
85
86impl From<f32> for MacroDecimal {
87 fn from(val: f32) -> Self {
88 MacroDecimal::Value(val as f64)
89 }
90}
91
92impl From<f64> for MacroDecimal {
93 fn from(val: f64) -> Self {
94 MacroDecimal::Value(val)
95 }
96}
97
98impl<W: Write> PartialGerberCode<W> for MacroDecimal {
99 fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
100 match *self {
101 MacroDecimal::Value(ref v) => write!(writer, "{}", v)?,
102 MacroDecimal::Variable(ref v) => write!(writer, "${}", v)?,
103 MacroDecimal::Expression(ref v) => write!(writer, "{}", v)?,
104 };
105 Ok(())
106 }
107}
108
109#[derive(Debug, Clone, PartialEq)]
113pub enum MacroBoolean {
114 Value(bool),
115 Variable(u32),
116 Expression(String),
117}
118
119impl<W: Write> PartialGerberCode<W> for MacroBoolean {
120 fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
121 match *self {
122 MacroBoolean::Value(ref v) => write!(writer, "{}", *v as u8)?,
123 MacroBoolean::Variable(ref v) => write!(writer, "${}", v)?,
124 MacroBoolean::Expression(ref v) => write!(writer, "{}", v)?,
125 };
126 Ok(())
127 }
128}
129
130impl From<MacroDecimal> for MacroBoolean {
131 fn from(value: MacroDecimal) -> Self {
132 match value {
133 MacroDecimal::Value(decimal) => Self::Value(decimal == 1.0),
134 MacroDecimal::Variable(variable) => Self::Variable(variable),
135 MacroDecimal::Expression(expressions) => Self::Expression(expressions),
136 }
137 }
138}
139
140#[test]
141fn test_macro_boolean_from_decimal() {
142 assert_eq!(
143 MacroBoolean::from(MacroDecimal::Value(1.0)),
144 MacroBoolean::Value(true)
145 );
146 assert_eq!(
147 MacroBoolean::from(MacroDecimal::Value(0.0)),
148 MacroBoolean::Value(false)
149 );
150 assert_eq!(
151 MacroBoolean::from(MacroDecimal::Variable(42)),
152 MacroBoolean::Variable(42)
153 );
154 assert_eq!(
155 MacroBoolean::from(MacroDecimal::Expression("$1x$2".to_string())),
156 MacroBoolean::Expression("$1x$2".to_string())
157 );
158}
159
160#[derive(Debug, Clone, PartialEq)]
163pub enum MacroInteger {
164 Value(u32),
165 Variable(u32),
166 Expression(String),
167}
168
169impl<W: Write> PartialGerberCode<W> for MacroInteger {
170 fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
171 match *self {
172 MacroInteger::Value(ref v) => write!(writer, "{}", v)?,
173 MacroInteger::Variable(ref v) => write!(writer, "${}", v)?,
174 MacroInteger::Expression(ref v) => write!(writer, "{}", v)?,
175 };
176 Ok(())
177 }
178}
179
180#[derive(Debug, Clone, PartialEq)]
192pub enum MacroContent {
193 Circle(CirclePrimitive),
195 VectorLine(VectorLinePrimitive),
196 CenterLine(CenterLinePrimitive),
197 Outline(OutlinePrimitive),
198 Polygon(PolygonPrimitive),
199 Moire(MoirePrimitive),
200 Thermal(ThermalPrimitive),
201
202 VariableDefinition(VariableDefinition),
204
205 Comment(String),
207}
208
209impl<W: Write> PartialGerberCode<W> for MacroContent {
210 fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
211 match *self {
212 MacroContent::Circle(ref c) => c.serialize_partial(writer)?,
213 MacroContent::VectorLine(ref vl) => vl.serialize_partial(writer)?,
214 MacroContent::CenterLine(ref cl) => cl.serialize_partial(writer)?,
215 MacroContent::Outline(ref o) => o.serialize_partial(writer)?,
216 MacroContent::Polygon(ref p) => p.serialize_partial(writer)?,
217 MacroContent::Moire(ref m) => m.serialize_partial(writer)?,
218 MacroContent::Thermal(ref t) => t.serialize_partial(writer)?,
219 MacroContent::Comment(ref s) => write!(writer, "0 {}*", &s)?,
220 MacroContent::VariableDefinition(ref v) => v.serialize_partial(writer)?,
221 };
222 Ok(())
223 }
224}
225
226macro_rules! impl_into {
227 ($target:ty, $from:ty, $choice:expr) => {
228 impl From<$from> for $target {
229 fn from(val: $from) -> $target {
230 $choice(val)
231 }
232 }
233 };
234}
235
236impl_into!(MacroContent, CirclePrimitive, MacroContent::Circle);
237impl_into!(MacroContent, VectorLinePrimitive, MacroContent::VectorLine);
238impl_into!(MacroContent, CenterLinePrimitive, MacroContent::CenterLine);
239impl_into!(MacroContent, OutlinePrimitive, MacroContent::Outline);
240impl_into!(MacroContent, PolygonPrimitive, MacroContent::Polygon);
241impl_into!(MacroContent, MoirePrimitive, MacroContent::Moire);
242impl_into!(MacroContent, ThermalPrimitive, MacroContent::Thermal);
243impl_into!(
244 MacroContent,
245 VariableDefinition,
246 MacroContent::VariableDefinition
247);
248
249impl<T: Into<String>> From<T> for MacroContent {
250 fn from(val: T) -> Self {
251 MacroContent::Comment(val.into())
252 }
253}
254
255#[derive(Debug, Clone, PartialEq)]
256pub struct CirclePrimitive {
257 pub exposure: MacroBoolean,
259
260 pub diameter: MacroDecimal,
262
263 pub center: (MacroDecimal, MacroDecimal),
265
266 pub angle: Option<MacroDecimal>,
275}
276
277impl CirclePrimitive {
278 pub fn new(diameter: MacroDecimal) -> Self {
279 CirclePrimitive {
280 exposure: MacroBoolean::Value(true),
281 diameter,
282 center: (MacroDecimal::Value(0.0), MacroDecimal::Value(0.0)),
283 angle: None,
284 }
285 }
286
287 pub fn centered_at(mut self, center: (MacroDecimal, MacroDecimal)) -> Self {
288 self.center = center;
289 self
290 }
291
292 pub fn with_exposure(mut self, exposure: MacroBoolean) -> Self {
293 self.exposure = exposure;
294 self
295 }
296
297 #[deprecated(since = "0.4.0", note = "Use `with_exposure` instead")]
298 #[allow(unused_mut)]
299 pub fn exposure_on(mut self, exposure: bool) -> Self {
300 self.with_exposure(MacroBoolean::Value(exposure))
301 }
302
303 pub fn with_angle(mut self, angle: MacroDecimal) -> Self {
304 self.angle = Some(angle);
305 self
306 }
307}
308
309impl<W: Write> PartialGerberCode<W> for CirclePrimitive {
310 fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
311 write!(writer, "1,")?;
312 self.exposure.serialize_partial(writer)?;
313 write!(writer, ",")?;
314 self.diameter.serialize_partial(writer)?;
315 write!(writer, ",")?;
316 self.center.0.serialize_partial(writer)?;
317 write!(writer, ",")?;
318 self.center.1.serialize_partial(writer)?;
319 if let Some(ref a) = self.angle {
320 write!(writer, ",")?;
321 a.serialize_partial(writer)?;
322 }
323 write!(writer, "*")?;
324 Ok(())
325 }
326}
327
328#[derive(Debug, Clone, PartialEq)]
329pub struct VectorLinePrimitive {
330 pub exposure: MacroBoolean,
332
333 pub width: MacroDecimal,
335
336 pub start: (MacroDecimal, MacroDecimal),
338
339 pub end: (MacroDecimal, MacroDecimal),
341
342 pub angle: MacroDecimal,
348}
349
350impl VectorLinePrimitive {
351 pub fn new(start: (MacroDecimal, MacroDecimal), end: (MacroDecimal, MacroDecimal)) -> Self {
352 VectorLinePrimitive {
353 exposure: MacroBoolean::Value(true),
354 width: MacroDecimal::Value(0.0),
355 start,
356 end,
357 angle: MacroDecimal::Value(0.0),
358 }
359 }
360
361 pub fn with_exposure(mut self, exposure: MacroBoolean) -> Self {
362 self.exposure = exposure;
363 self
364 }
365
366 #[deprecated(since = "0.4.0", note = "Use `with_exposure` instead")]
367 #[allow(unused_mut)]
368 pub fn exposure_on(mut self, exposure: bool) -> Self {
369 self.with_exposure(MacroBoolean::Value(exposure))
370 }
371
372 pub fn with_width(mut self, width: MacroDecimal) -> Self {
373 self.width = width;
374 self
375 }
376
377 pub fn with_angle(mut self, angle: MacroDecimal) -> Self {
378 self.angle = angle;
379 self
380 }
381}
382
383impl<W: Write> PartialGerberCode<W> for VectorLinePrimitive {
384 fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
385 write!(writer, "20,")?;
386 self.exposure.serialize_partial(writer)?;
387 write!(writer, ",")?;
388 self.width.serialize_partial(writer)?;
389 write!(writer, ",")?;
390 self.start.0.serialize_partial(writer)?;
391 write!(writer, ",")?;
392 self.start.1.serialize_partial(writer)?;
393 write!(writer, ",")?;
394 self.end.0.serialize_partial(writer)?;
395 write!(writer, ",")?;
396 self.end.1.serialize_partial(writer)?;
397 write!(writer, ",")?;
398 self.angle.serialize_partial(writer)?;
399 write!(writer, "*")?;
400 Ok(())
401 }
402}
403
404#[derive(Debug, Clone, PartialEq)]
405pub struct CenterLinePrimitive {
406 pub exposure: MacroBoolean,
408
409 pub dimensions: (MacroDecimal, MacroDecimal),
411
412 pub center: (MacroDecimal, MacroDecimal),
414
415 pub angle: MacroDecimal,
421}
422
423impl CenterLinePrimitive {
424 pub fn new(dimensions: (MacroDecimal, MacroDecimal)) -> Self {
425 CenterLinePrimitive {
426 exposure: MacroBoolean::Value(true),
427 dimensions,
428 center: (MacroDecimal::Value(0.0), MacroDecimal::Value(0.0)),
429 angle: MacroDecimal::Value(0.0),
430 }
431 }
432
433 pub fn with_exposure(mut self, exposure: MacroBoolean) -> Self {
434 self.exposure = exposure;
435 self
436 }
437
438 #[deprecated(since = "0.4.0", note = "Use `with_exposure` instead")]
439 #[allow(unused_mut)]
440 pub fn exposure_on(mut self, exposure: bool) -> Self {
441 self.with_exposure(MacroBoolean::Value(exposure))
442 }
443
444 pub fn centered_at(mut self, center: (MacroDecimal, MacroDecimal)) -> Self {
445 self.center = center;
446 self
447 }
448
449 pub fn with_angle(mut self, angle: MacroDecimal) -> Self {
450 self.angle = angle;
451 self
452 }
453}
454
455impl<W: Write> PartialGerberCode<W> for CenterLinePrimitive {
456 fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
457 write!(writer, "21,")?;
458 self.exposure.serialize_partial(writer)?;
459 write!(writer, ",")?;
460 self.dimensions.0.serialize_partial(writer)?;
461 write!(writer, ",")?;
462 self.dimensions.1.serialize_partial(writer)?;
463 write!(writer, ",")?;
464 self.center.0.serialize_partial(writer)?;
465 write!(writer, ",")?;
466 self.center.1.serialize_partial(writer)?;
467 write!(writer, ",")?;
468 self.angle.serialize_partial(writer)?;
469 write!(writer, "*")?;
470 Ok(())
471 }
472}
473
474#[derive(Debug, Clone, PartialEq)]
475pub struct OutlinePrimitive {
476 pub exposure: MacroBoolean,
478
479 pub points: Vec<(MacroDecimal, MacroDecimal)>,
483
484 pub angle: MacroDecimal,
490}
491
492impl OutlinePrimitive {
493 pub fn new() -> Self {
494 OutlinePrimitive {
495 exposure: MacroBoolean::Value(true),
496 points: Vec::new(),
497 angle: MacroDecimal::Value(0.0),
498 }
499 }
500
501 pub fn from_points(points: Vec<(MacroDecimal, MacroDecimal)>) -> Self {
502 let mut outline_prim = Self::new();
503 outline_prim.points = points;
504 outline_prim
505 }
506
507 pub fn add_point(mut self, point: (MacroDecimal, MacroDecimal)) -> Self {
508 self.points.push(point);
509 self
510 }
511
512 pub fn with_angle(mut self, angle: MacroDecimal) -> Self {
513 self.angle = angle;
514 self
515 }
516}
517
518impl<W: Write> PartialGerberCode<W> for OutlinePrimitive {
519 fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
520 if self.points.len() < 2 {
522 return Err(GerberError::MissingDataError(
523 "There must be at least 1 subsequent point in an outline".into(),
524 ));
525 }
526 if self.points.len() > 5001 {
527 return Err(GerberError::RangeError(
528 "The maximum number of subsequent points in an outline is 5000".into(),
529 ));
530 }
531 if self.points[0] != self.points[self.points.len() - 1] {
532 return Err(GerberError::RangeError(
533 "The last point must be equal to the first point".into(),
534 ));
535 }
536
537 write!(writer, "4,")?;
538 self.exposure.serialize_partial(writer)?;
539 writeln!(writer, ",{},", self.points.len() - 1)?;
540
541 for (ref x, ref y) in &self.points {
542 x.serialize_partial(writer)?;
543 write!(writer, ",")?;
544 y.serialize_partial(writer)?;
545 writeln!(writer, ",")?;
546 }
547 self.angle.serialize_partial(writer)?;
548 write!(writer, "*")?;
549 Ok(())
550 }
551}
552
553#[derive(Debug, Clone, PartialEq)]
554pub struct PolygonPrimitive {
557 pub exposure: MacroBoolean,
559
560 pub vertices: MacroInteger,
562
563 pub center: (MacroDecimal, MacroDecimal),
565
566 pub diameter: MacroDecimal,
568
569 pub angle: MacroDecimal,
579}
580
581impl PolygonPrimitive {
582 pub fn new(vertices: MacroInteger) -> Self {
583 PolygonPrimitive {
584 exposure: MacroBoolean::Value(true),
585 vertices,
586 center: (MacroDecimal::Value(0.0), MacroDecimal::Value(0.0)),
587 diameter: MacroDecimal::Value(0.0),
588 angle: MacroDecimal::Value(0.0),
589 }
590 }
591
592 pub fn with_exposure(mut self, exposure: MacroBoolean) -> Self {
593 self.exposure = exposure;
594 self
595 }
596
597 #[deprecated(since = "0.4.0", note = "Use `with_exposure` instead")]
598 #[allow(unused_mut)]
599 pub fn exposure_on(mut self, exposure: bool) -> Self {
600 self.with_exposure(MacroBoolean::Value(exposure))
601 }
602
603 pub fn centered_at(mut self, center: (MacroDecimal, MacroDecimal)) -> Self {
604 self.center = center;
605 self
606 }
607
608 pub fn with_diameter(mut self, diameter: MacroDecimal) -> Self {
609 self.diameter = diameter;
610 self
611 }
612
613 pub fn with_angle(mut self, angle: MacroDecimal) -> Self {
614 self.angle = angle;
615 self
616 }
617}
618
619impl<W: Write> PartialGerberCode<W> for PolygonPrimitive {
620 fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
621 if matches!(self.vertices, MacroInteger::Value(value) if value < 3) {
623 return Err(GerberError::MissingDataError(
624 "There must be at least 3 vertices in a polygon".into(),
625 ));
626 }
627 if matches!(self.vertices, MacroInteger::Value(value) if value > 12) {
628 return Err(GerberError::RangeError(
629 "The maximum number of vertices in a polygon is 12".into(),
630 ));
631 }
632 if self.diameter.is_negative() {
633 return Err(GerberError::RangeError(
634 "The diameter must not be negative".into(),
635 ));
636 }
637 write!(writer, "5,")?;
638 self.exposure.serialize_partial(writer)?;
639 write!(writer, ",")?;
640 self.vertices.serialize_partial(writer)?;
641 write!(writer, ",")?;
642 self.center.0.serialize_partial(writer)?;
643 write!(writer, ",")?;
644 self.center.1.serialize_partial(writer)?;
645 write!(writer, ",")?;
646 self.diameter.serialize_partial(writer)?;
647 write!(writer, ",")?;
648 self.angle.serialize_partial(writer)?;
649 write!(writer, "*")?;
650 Ok(())
651 }
652}
653
654#[derive(Debug, Clone, PartialEq)]
657pub struct MoirePrimitive {
658 pub center: (MacroDecimal, MacroDecimal),
660
661 pub diameter: MacroDecimal,
663
664 pub ring_thickness: MacroDecimal,
666
667 pub gap: MacroDecimal,
669
670 pub max_rings: u32,
672
673 pub cross_hair_thickness: MacroDecimal,
675
676 pub cross_hair_length: MacroDecimal,
678
679 pub angle: MacroDecimal,
688}
689
690impl MoirePrimitive {
691 pub fn new() -> Self {
692 MoirePrimitive {
693 center: (MacroDecimal::Value(0.0), MacroDecimal::Value(0.0)),
694 diameter: MacroDecimal::Value(0.0),
695 ring_thickness: MacroDecimal::Value(0.0),
696 gap: MacroDecimal::Value(0.0),
697 max_rings: 1,
698 cross_hair_thickness: MacroDecimal::Value(0.0),
699 cross_hair_length: MacroDecimal::Value(0.0),
700 angle: MacroDecimal::Value(0.0),
701 }
702 }
703
704 pub fn centered_at(mut self, center: (MacroDecimal, MacroDecimal)) -> Self {
705 self.center = center;
706 self
707 }
708
709 pub fn with_diameter(mut self, diameter: MacroDecimal) -> Self {
710 self.diameter = diameter;
711 self
712 }
713
714 pub fn with_rings_max(mut self, max_rings: u32) -> Self {
715 self.max_rings = max_rings;
716 self
717 }
718
719 pub fn with_ring_thickness(mut self, thickness: MacroDecimal) -> Self {
720 self.ring_thickness = thickness;
721 self
722 }
723
724 pub fn with_gap(mut self, gap: MacroDecimal) -> Self {
725 self.gap = gap;
726 self
727 }
728
729 pub fn with_cross_thickness(mut self, thickness: MacroDecimal) -> Self {
730 self.cross_hair_thickness = thickness;
731 self
732 }
733
734 pub fn with_cross_length(mut self, length: MacroDecimal) -> Self {
735 self.cross_hair_length = length;
736 self
737 }
738
739 pub fn with_angle(mut self, angle: MacroDecimal) -> Self {
740 self.angle = angle;
741 self
742 }
743}
744
745impl<W: Write> PartialGerberCode<W> for MoirePrimitive {
746 fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
747 if self.diameter.is_negative() {
749 return Err(GerberError::RangeError(
750 "Outer diameter of a moiré may not be negative".into(),
751 ));
752 }
753 if self.ring_thickness.is_negative() {
754 return Err(GerberError::RangeError(
755 "Ring thickness of a moiré may not be negative".into(),
756 ));
757 }
758 if self.gap.is_negative() {
759 return Err(GerberError::RangeError(
760 "Gap of a moiré may not be negative".into(),
761 ));
762 }
763 if self.cross_hair_thickness.is_negative() {
764 return Err(GerberError::RangeError(
765 "Cross hair thickness of a moiré may not be negative".into(),
766 ));
767 }
768 if self.cross_hair_length.is_negative() {
769 return Err(GerberError::RangeError(
770 "Cross hair length of a moiré may not be negative".into(),
771 ));
772 }
773 write!(writer, "6,")?;
774 self.center.0.serialize_partial(writer)?;
775 write!(writer, ",")?;
776 self.center.1.serialize_partial(writer)?;
777 write!(writer, ",")?;
778 self.diameter.serialize_partial(writer)?;
779 write!(writer, ",")?;
780 self.ring_thickness.serialize_partial(writer)?;
781 write!(writer, ",")?;
782 self.gap.serialize_partial(writer)?;
783 write!(writer, ",{},", self.max_rings)?;
784 self.cross_hair_thickness.serialize_partial(writer)?;
785 write!(writer, ",")?;
786 self.cross_hair_length.serialize_partial(writer)?;
787 write!(writer, ",")?;
788 self.angle.serialize_partial(writer)?;
789 write!(writer, "*")?;
790 Ok(())
791 }
792}
793
794#[derive(Debug, Clone, PartialEq)]
797pub struct ThermalPrimitive {
798 pub center: (MacroDecimal, MacroDecimal),
800
801 pub outer_diameter: MacroDecimal,
803
804 pub inner_diameter: MacroDecimal,
806
807 pub gap: MacroDecimal,
809
810 pub angle: MacroDecimal,
820}
821
822impl ThermalPrimitive {
823 pub fn new(inner: MacroDecimal, outer: MacroDecimal, gap: MacroDecimal) -> Self {
824 ThermalPrimitive {
825 center: (MacroDecimal::Value(0.0), MacroDecimal::Value(0.0)),
826 outer_diameter: outer,
827 inner_diameter: inner,
828 gap,
829 angle: MacroDecimal::Value(0.0),
830 }
831 }
832
833 pub fn centered_at(mut self, center: (MacroDecimal, MacroDecimal)) -> Self {
834 self.center = center;
835 self
836 }
837
838 pub fn with_angle(mut self, angle: MacroDecimal) -> Self {
839 self.angle = angle;
840 self
841 }
842}
843
844impl<W: Write> PartialGerberCode<W> for ThermalPrimitive {
845 fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
846 if self.inner_diameter.is_negative() {
848 return Err(GerberError::RangeError(
849 "Inner diameter of a thermal may not be negative".into(),
850 ));
851 }
852 write!(writer, "7,")?;
853 self.center.0.serialize_partial(writer)?;
854 write!(writer, ",")?;
855 self.center.1.serialize_partial(writer)?;
856 write!(writer, ",")?;
857 self.outer_diameter.serialize_partial(writer)?;
858 write!(writer, ",")?;
859 self.inner_diameter.serialize_partial(writer)?;
860 write!(writer, ",")?;
861 self.gap.serialize_partial(writer)?;
862 write!(writer, ",")?;
863 self.angle.serialize_partial(writer)?;
864 write!(writer, "*")?;
865 Ok(())
866 }
867}
868
869#[derive(Debug, Clone, PartialEq, Eq)]
870pub struct VariableDefinition {
871 pub number: u32,
872 pub expression: String,
873}
874
875impl VariableDefinition {
876 pub fn new(number: u32, expr: &str) -> Self {
877 VariableDefinition {
878 number,
879 expression: expr.into(),
880 }
881 }
882}
883
884impl<W: Write> PartialGerberCode<W> for VariableDefinition {
885 fn serialize_partial(&self, writer: &mut W) -> GerberResult<()> {
886 write!(writer, "${}={}*", self.number, self.expression)?;
887 Ok(())
888 }
889}
890
891#[cfg(test)]
892mod test {
893 use std::io::BufWriter;
894
895 use crate::traits::PartialGerberCode;
896
897 use super::MacroDecimal::{Expression, Value, Variable};
898 use super::*;
899
900 macro_rules! assert_partial_code {
901 ($obj:expr, $expected:expr) => {
902 let mut buf = BufWriter::new(Vec::new());
903 $obj.serialize_partial(&mut buf)
904 .expect("Could not generate Gerber code");
905 let bytes = buf.into_inner().unwrap();
906 let code = String::from_utf8(bytes).unwrap();
907 assert_eq!(&code, $expected);
908 };
909 }
910
911 #[test]
912 fn test_circle_primitive_codegen() {
913 let with_angle = CirclePrimitive {
914 exposure: MacroBoolean::Value(true),
915 diameter: Value(1.5),
916 center: (Value(0.), Value(0.)),
917 angle: Some(Value(0.)),
918 };
919 assert_partial_code!(with_angle, "1,1,1.5,0,0,0*");
920 let no_angle = CirclePrimitive {
921 exposure: MacroBoolean::Value(false),
922 diameter: Value(99.9),
923 center: (Value(1.1), Value(2.2)),
924 angle: None,
925 };
926 assert_partial_code!(no_angle, "1,0,99.9,1.1,2.2*");
927 }
928
929 #[test]
930 fn test_vector_line_primitive_codegen() {
931 let line = VectorLinePrimitive {
932 exposure: MacroBoolean::Value(true),
933 width: Value(0.9),
934 start: (Value(0.), Value(0.45)),
935 end: (Value(12.), Value(0.45)),
936 angle: Value(0.),
937 };
938 assert_partial_code!(line, "20,1,0.9,0,0.45,12,0.45,0*");
939 }
940
941 #[test]
942 fn test_center_line_primitive_codegen() {
943 let line = CenterLinePrimitive {
944 exposure: MacroBoolean::Value(true),
945 dimensions: (Value(6.8), Value(1.2)),
946 center: (Value(3.4), Value(0.6)),
947 angle: Value(30.0),
948 };
949 assert_partial_code!(line, "21,1,6.8,1.2,3.4,0.6,30*");
950 }
951
952 #[test]
953 fn test_outline_primitive_codegen() {
954 let line = OutlinePrimitive {
955 exposure: MacroBoolean::Value(true),
956 points: vec![
957 (Value(0.1), Value(0.1)),
958 (Value(0.5), Value(0.1)),
959 (Value(0.5), Value(0.5)),
960 (Value(0.1), Value(0.5)),
961 (Value(0.1), Value(0.1)),
962 ],
963 angle: Value(0.0),
964 };
965 assert_partial_code!(
966 line,
967 "4,1,4,\n0.1,0.1,\n0.5,0.1,\n0.5,0.5,\n0.1,0.5,\n0.1,0.1,\n0*"
968 );
969 }
970
971 #[test]
972 fn test_polygon_primitive_codegen() {
973 let line = PolygonPrimitive {
974 exposure: MacroBoolean::Value(true),
975 vertices: MacroInteger::Value(8),
976 center: (Value(1.5), Value(2.0)),
977 diameter: Value(8.0),
978 angle: Value(0.0),
979 };
980 assert_partial_code!(line, "5,1,8,1.5,2,8,0*");
981 }
982
983 #[test]
984 fn test_moire_primitive_codegen() {
985 let line = MoirePrimitive {
986 center: (Value(0.0), Value(0.0)),
987 diameter: Value(5.0),
988 ring_thickness: Value(0.5),
989 gap: Value(0.5),
990 max_rings: 2,
991 cross_hair_thickness: Value(0.1),
992 cross_hair_length: Value(6.0),
993 angle: Value(0.0),
994 };
995 assert_partial_code!(line, "6,0,0,5,0.5,0.5,2,0.1,6,0*");
996 }
997
998 #[test]
999 fn test_thermal_primitive_codegen() {
1000 let line = ThermalPrimitive {
1001 center: (Value(0.0), Value(0.0)),
1002 outer_diameter: Value(8.0),
1003 inner_diameter: Value(6.5),
1004 gap: Value(1.0),
1005 angle: Value(45.0),
1006 };
1007 assert_partial_code!(line, "7,0,0,8,6.5,1,45*");
1008 }
1009
1010 #[test]
1011 fn test_aperture_macro_codegen() {
1012 let am = ApertureMacro::new("CRAZY")
1013 .add_content(MacroContent::Thermal(ThermalPrimitive {
1014 center: (Value(0.0), Value(0.0)),
1015 outer_diameter: Value(0.08),
1016 inner_diameter: Value(0.055),
1017 gap: Value(0.0125),
1018 angle: Value(45.0),
1019 }))
1020 .add_content(MacroContent::Moire(MoirePrimitive {
1021 center: (Value(0.0), Value(0.0)),
1022 diameter: Value(0.125),
1023 ring_thickness: Value(0.01),
1024 gap: Value(0.01),
1025 max_rings: 3,
1026 cross_hair_thickness: Value(0.003),
1027 cross_hair_length: Value(0.150),
1028 angle: Value(0.0),
1029 }));
1030 assert_partial_code!(
1031 am,
1032 "AMCRAZY*\n7,0,0,0.08,0.055,0.0125,45*\n6,0,0,0.125,0.01,0.01,3,0.003,0.15,0*"
1033 );
1034 }
1035
1036 #[test]
1038 fn test_codegen_with_variable() {
1039 let line = VectorLinePrimitive {
1040 exposure: MacroBoolean::Value(true),
1041 width: Variable(0),
1042 start: (Variable(1), 0.45.into()),
1043 end: (Value(12.), Expression("$2x4".to_string())),
1044 angle: Variable(3),
1045 };
1046 assert_partial_code!(line, "20,1,$0,$1,0.45,12,$2x4,$3*");
1047 }
1048
1049 #[test]
1050 fn test_macro_decimal_into() {
1051 let a = Value(1.0);
1052 let b: MacroDecimal = 1.0.into();
1053 assert_eq!(a, b);
1054 let c = Variable(1);
1055 let d = Variable(1);
1056 assert_eq!(c, d);
1057 }
1058
1059 #[test]
1060 fn test_comment_codegen() {
1061 let comment = MacroContent::Comment("hello world".to_string());
1062 assert_partial_code!(comment, "0 hello world*");
1063 }
1064
1065 #[test]
1066 fn test_variable_definition_codegen() {
1067 let var = VariableDefinition {
1068 number: 17,
1069 expression: "$40+2".to_string(),
1070 };
1071 assert_partial_code!(var, "$17=$40+2*");
1072 }
1073
1074 #[test]
1075 fn test_macrocontent_from_into() {
1076 let a = MacroContent::Comment("hello".into());
1077 let b: MacroContent = "hello".to_string().into();
1078 let c: MacroContent = "hello".into();
1079 assert_eq!(a, b);
1080 assert_eq!(b, c);
1081 }
1082
1083 #[test]
1084 fn test_circle_primitive_new() {
1085 let c1 = CirclePrimitive::new(Value(3.0)).centered_at((Value(5.0), Value(0.0)));
1086 let c2 = CirclePrimitive {
1087 exposure: MacroBoolean::Value(true),
1088 diameter: Value(3.0),
1089 center: (Value(5.0), Value(0.0)),
1090 angle: None,
1091 };
1092 assert_eq!(c1, c2);
1093 }
1094
1095 #[test]
1096 fn test_vectorline_primitive_new() {
1097 let vl1 = VectorLinePrimitive::new((Value(0.0), Value(5.3)), (Value(3.9), Value(8.5)))
1098 .with_angle(Value(38.0));
1099 let vl2 = VectorLinePrimitive {
1100 exposure: MacroBoolean::Value(true),
1101 width: Value(0.0),
1102 start: (Value(0.0), Value(5.3)),
1103 end: (Value(3.9), Value(8.5)),
1104 angle: Value(38.0),
1105 };
1106 assert_eq!(vl1, vl2);
1107 }
1108
1109 #[test]
1110 fn test_centerline_primitive_new() {
1111 let cl1 = CenterLinePrimitive::new((Value(3.0), Value(4.5)))
1112 .with_exposure(MacroBoolean::Value(false));
1113 let cl2 = CenterLinePrimitive {
1114 exposure: MacroBoolean::Value(false),
1115 dimensions: (Value(3.0), Value(4.5)),
1116 center: (Value(0.0), Value(0.0)),
1117 angle: Value(0.0),
1118 };
1119 assert_eq!(cl1, cl2);
1120 }
1121
1122 #[test]
1123 fn test_outline_primitive_new() {
1124 let op1 = OutlinePrimitive::new()
1125 .add_point((Value(0.0), Value(0.0)))
1126 .add_point((Value(2.0), Value(2.0)))
1127 .add_point((Value(-2.0), Value(-2.0)))
1128 .add_point((Value(0.0), Value(0.0)));
1129
1130 let pts = vec![
1131 (Value(0.0), Value(0.0)),
1132 (Value(2.0), Value(2.0)),
1133 (Value(-2.0), Value(-2.0)),
1134 (Value(0.0), Value(0.0)),
1135 ];
1136
1137 let op2 = OutlinePrimitive {
1138 exposure: MacroBoolean::Value(true),
1139 points: pts,
1140 angle: Value(0.0),
1141 };
1142 assert_eq!(op1, op2);
1143 }
1144
1145 #[test]
1146 fn test_polygon_primitive_new() {
1147 let pp1 = PolygonPrimitive::new(MacroInteger::Value(5))
1148 .with_angle(Value(98.0))
1149 .with_diameter(Value(5.3))
1150 .centered_at((Value(1.0), Value(1.0)));
1151 let pp2 = PolygonPrimitive {
1152 exposure: MacroBoolean::Value(true),
1153 vertices: MacroInteger::Value(5),
1154 angle: Value(98.0),
1155 diameter: Value(5.3),
1156 center: (Value(1.0), Value(1.0)),
1157 };
1158 assert_eq!(pp1, pp2);
1159 }
1160
1161 #[test]
1162 fn test_moire_primitive_new() {
1163 let mp1 = MoirePrimitive::new()
1164 .with_diameter(Value(3.0))
1165 .with_ring_thickness(Value(0.05))
1166 .with_cross_thickness(Value(0.01))
1167 .with_cross_length(Value(0.5))
1168 .with_rings_max(3);
1169 let mp2 = MoirePrimitive {
1170 center: (MacroDecimal::Value(0.0), MacroDecimal::Value(0.0)),
1171 diameter: MacroDecimal::Value(3.0),
1172 ring_thickness: MacroDecimal::Value(0.05),
1173 gap: MacroDecimal::Value(0.0),
1174 max_rings: 3,
1175 cross_hair_thickness: MacroDecimal::Value(0.01),
1176 cross_hair_length: MacroDecimal::Value(0.5),
1177 angle: MacroDecimal::Value(0.0),
1178 };
1179 assert_eq!(mp1, mp2);
1180 }
1181
1182 #[test]
1183 fn test_thermal_primitive_new() {
1184 let tp1 = ThermalPrimitive::new(Value(1.0), Value(2.0), Value(1.5)).with_angle(Value(87.3));
1185 let tp2 = ThermalPrimitive {
1186 inner_diameter: Value(1.0),
1187 outer_diameter: Value(2.0),
1188 gap: Value(1.5),
1189 angle: Value(87.3),
1190 center: (Value(0.0), Value(0.0)),
1191 };
1192 assert_eq!(tp1, tp2);
1193 }
1194
1195 #[test]
1196 fn test_variabledefinition_new() {
1197 let vd1 = VariableDefinition::new(3, "Test!");
1198 let vd2 = VariableDefinition {
1199 number: 3,
1200 expression: "Test!".into(),
1201 };
1202 assert_eq!(vd1, vd2);
1203 }
1204}