kicad_parse_gen/footprint/
data.rs

1// (c) 2016-2017 Productize SPRL <joost@productize.be>
2
3use std::f64::EPSILON;
4
5use {Adjust, Bound, BoundingBox};
6
7pub use layout::NetName;
8
9use checkfix::{CheckFix, CheckFixData, Config};
10
11use symbolic_expressions::SexpError;
12
13/// implement to allow a Module and it's sub Element be flippable
14pub trait Flip {
15    /// flip me
16    fn flip(&mut self);
17}
18
19/// rotate a module to be able to compare rotated modules
20pub trait Rotate {
21    /// rotate
22    fn rotate(&mut self, rot: f64);
23}
24
25/// a Kicad module, with a name and a list of elements
26#[derive(Debug, Clone)]
27pub struct Module {
28    /// name of the Kicad Module
29    pub name: String,
30    /// elements contained within the Kicad Module
31    pub elements: Vec<Element>,
32}
33
34trait Named {
35    fn name(&self) -> &'static str;
36}
37
38impl Module {
39    /// create a Module
40    pub fn new(name: String) -> Module {
41        Module {
42            name: name,
43            elements: vec![],
44        }
45    }
46    /// append an Element to a Module
47    pub fn append(&mut self, e: Element) {
48        self.elements.push(e)
49    }
50    /// check if a Module has a reference Element with the specified name
51    pub fn is_reference_with_name(&self, reference: &str) -> bool {
52        for element in &self.elements {
53            if let Element::FpText(ref fp_text) = *element {
54                if fp_text.name == "reference" && fp_text.value == *reference {
55                    return true;
56                }
57            }
58        }
59        false
60    }
61
62    /// get the Reference element from the Module if it exists
63    pub fn get_reference(&self) -> Option<&String> {
64        self.get_reference_text().map(|a| &a.value)
65    }
66
67    /// get a `FpText` field by name
68    pub fn get_text(&self, which: &'static str) -> Option<&FpText> {
69        for element in &self.elements {
70            if let Element::FpText(ref fp_text) = *element {
71                if fp_text.name == which {
72                    return Some(fp_text);
73                }
74            }
75        }
76        None
77    }
78
79    /// mutably get a `FpText` field by name
80    pub fn get_text_mut(&mut self, which: &'static str) -> Option<&mut FpText> {
81        for element in &mut self.elements {
82            if let Element::FpText(ref mut fp_text) = *element {
83                if fp_text.name == which {
84                    return Some(fp_text);
85                }
86            }
87        }
88        None
89    }
90
91    /// get the reference `FpText`
92    pub fn get_reference_text(&self) -> Option<&FpText> {
93        self.get_text("reference")
94    }
95
96    /// mutably get the reference `FpText`
97    pub fn get_reference_text_mut(&mut self) -> Option<&mut FpText> {
98        self.get_text_mut("reference")
99    }
100
101    /// get the reference2 `FpText`
102    pub fn get_reference2_text(&self) -> Option<&FpText> {
103        self.get_text("user")
104    }
105
106    /// mutably get the reference2 `FpText`
107    pub fn get_reference2_text_mut(&mut self) -> Option<&mut FpText> {
108        self.get_text_mut("user")
109    }
110
111    /// mutably get the value `FpText`
112    pub fn get_value_text_mut(&mut self) -> Option<&mut FpText> {
113        self.get_text_mut("value")
114    }
115
116
117    /// check if the module has the "smd" attribute
118    pub fn has_smd_attr(&self) -> bool {
119        for element in &self.elements {
120            if let Element::Attr(ref attr) = *element {
121                if attr.as_str() == "smd" {
122                    return true;
123                }
124            }
125        }
126        false
127    }
128
129    /// get the value `FpText`
130    pub fn get_value_text(&self) -> Option<&FpText> {
131        self.get_text("value")
132    }
133
134    /// check if a Module has a tstamp Element and return it
135    pub fn get_tstamp(&self) -> Option<i64> {
136        for element in &self.elements {
137            if let Element::TStamp(stamp) = *element {
138                return Some(stamp);
139            }
140        }
141        None
142    }
143
144    /// check if a Module has a tedit Element and return it
145    pub fn get_tedit(&self) -> Option<i64> {
146        for element in &self.elements {
147            if let Element::TEdit(stamp) = *element {
148                return Some(stamp);
149            }
150        }
151        None
152    }
153
154    /// update the name of the reference element specified by name, if found
155    pub fn set_reference(&mut self, reference: &str, reference2: &str) {
156        // println!("debug: searching '{}'", reference);
157        for element in &mut self.elements {
158            if let Element::FpText(ref mut fp_text) = *element {
159                if fp_text.name == "reference" && fp_text.value == *reference {
160                    fp_text.value.clear();
161                    fp_text.value.push_str(reference2);
162                }
163            }
164        }
165    }
166    /// check if there is an At element and return the coordinates found
167    /// returns the default of (0.0,0.0) if not found
168    pub fn at(&self) -> (f64, f64) {
169        for element in &self.elements {
170            if let Element::At(ref at) = *element {
171                return (at.x, at.y);
172            }
173        }
174        (0.0, 0.0)
175    }
176
177    /// check if there is an At element and return the rotation found
178    /// returns the default of 0.0 if not found
179    pub fn get_rotation(&self) -> f64 {
180        for element in &self.elements {
181            if let Element::At(ref at) = *element {
182                return at.rot;
183            }
184        }
185        0.0
186    }
187
188    /// adjust the At element contained in the module
189    pub fn adjust_at(&mut self, x: f64, y: f64) {
190        for element in &mut self.elements {
191            if let Element::At(ref mut at) = *element {
192                at.x += x;
193                at.y += y;
194            }
195        }
196    }
197
198    /// check if the Module is on the front layer
199    pub fn is_front(&self) -> bool {
200        for element in &self.elements {
201            if let Element::Layer(ref layer) = *element {
202                return layer.side == LayerSide::Front;
203            }
204        }
205        true
206    }
207
208    /// rename a net
209    pub fn rename_net(&mut self, old_name: &str, new_name: &str) {
210        for element in &mut self.elements {
211            if let Element::Pad(ref mut pad) = *element {
212                pad.rename_net(old_name, new_name)
213            }
214        }
215    }
216
217    /// return a list of `Pad`s contained in the module
218    pub fn pads(&self) -> Vec<&Pad> {
219        let mut v = vec![];
220        for element in &self.elements {
221            if let Element::Pad(ref pad) = *element {
222                v.push(pad)
223            }
224        }
225        v
226    }
227
228    /// return a list of `FpLine`s contained in the module
229    pub fn lines(&self) -> Vec<&FpLine> {
230        let mut v = vec![];
231        for element in &self.elements {
232            if let Element::FpLine(ref line) = *element {
233                v.push(line)
234            }
235        }
236        v
237    }
238}
239
240impl BoundingBox for Module {
241    fn bounding_box(&self) -> Bound {
242        let (x, y) = self.at();
243        let mut b = Bound::new(x, y, x, y);
244        for element in self.elements.iter().filter(|&x| x.is_graphics()) {
245            if element.is_fab() || element.is_pad() {
246                let mut b2 = element.bounding_box();
247                // make absolute
248                b2.x1 += x;
249                b2.y1 += y;
250                b2.x2 += x;
251                b2.y2 += y;
252                // trace!("{}: {:?}", element.name(), b2);
253                b.update(&b2);
254            }
255        }
256        b.swap_if_needed();
257        // trace!("Module {} bb: {:?}", self.name, b);
258        b
259    }
260}
261
262impl Adjust for Module {
263    fn adjust(&mut self, x: f64, y: f64) {
264        self.adjust_at(x, y)
265    }
266}
267
268impl Flip for Module {
269    fn flip(&mut self) {
270        for e in &mut self.elements {
271            e.flip()
272        }
273    }
274}
275
276impl Rotate for Module {
277    fn rotate(&mut self, rot: f64) {
278        for e in &mut self.elements {
279            e.rotate(rot)
280        }
281    }
282}
283
284/// elements that can be found in a Module
285#[derive(Debug, Clone, PartialEq)]
286pub enum Element {
287    /// solder mask margin
288    SolderMaskMargin(f64),
289    /// layer name
290    Layer(Layer),
291    /// description
292    Descr(String),
293    /// Tags element
294    Tags(String),
295    /// Attr element
296    Attr(String),
297    /// text
298    FpText(FpText),
299    /// pad
300    Pad(Pad),
301    /// polygon
302    FpPoly(FpPoly),
303    /// line
304    FpLine(FpLine),
305    /// circle
306    FpCircle(FpCircle),
307    /// arc
308    FpArc(FpArc),
309    /// edited time stamp
310    TEdit(i64),
311    /// time stamp
312    TStamp(i64),
313    /// Path element
314    Path(String),
315    /// location of module in layout
316    At(At),
317    /// 3D model information
318    Model(Model),
319    /// Clearance override for module
320    Clearance(f64),
321    /// is the module locked
322    Locked,
323}
324
325impl BoundingBox for Element {
326    fn bounding_box(&self) -> Bound {
327        match *self {
328            Element::Pad(ref x) => x.bounding_box(),
329            Element::FpPoly(ref x) => x.bounding_box(),
330            Element::FpLine(ref x) => x.bounding_box(),
331            Element::FpCircle(ref x) => x.bounding_box(),
332            Element::FpText(ref x) => x.bounding_box(),
333            Element::FpArc(ref x) => x.bounding_box(),
334            Element::At(_) |
335            Element::Layer(_) |
336            Element::TEdit(_) |
337            Element::Descr(_) |
338            Element::Path(_) |
339            Element::Model(_) |
340            Element::Attr(_) |
341            Element::SolderMaskMargin(_) |
342            Element::Clearance(_) |
343            Element::Tags(_) |
344            Element::Locked |
345            Element::TStamp(_) => Bound::default(),
346        }
347    }
348}
349
350impl Named for Element {
351    fn name(&self) -> &'static str {
352        match *self {
353            Element::Pad(_) => "Pad",
354            Element::FpPoly(_) => "FpPoly",
355            Element::FpLine(_) => "FpLine",
356            Element::FpCircle(_) => "FpCircle",
357            Element::FpArc(_) => "FpArc",
358            Element::FpText(_) => "FpText",
359            Element::At(_) => "At",
360            Element::Layer(_) => "Layer",
361            Element::TEdit(_) => "TEdit",
362            Element::Descr(_) => "Descr",
363            Element::Path(_) => "Path",
364            Element::Model(_) => "Model",
365            Element::TStamp(_) => "Tstamp",
366            Element::SolderMaskMargin(_) => "SolderMaskMargin",
367            Element::Clearance(_) => "Clearance",
368            Element::Tags(_) => "Tags",
369            Element::Attr(_) => "Attr",
370            Element::Locked => "Locked",
371        }
372    }
373}
374
375impl Flip for Element {
376    fn flip(&mut self) {
377        match *self {
378            Element::Pad(ref mut p) => p.flip(),
379            Element::FpPoly(ref mut p) => p.flip(),
380            Element::FpLine(ref mut p) => p.flip(),
381            Element::FpCircle(ref mut p) => p.flip(),
382            Element::FpArc(ref mut p) => p.flip(),
383            Element::FpText(ref mut p) => p.flip(),
384            Element::At(ref mut p) => p.flip(),
385            Element::Layer(ref mut p) => p.flip(),
386            Element::TEdit(_) |
387            Element::Descr(_) |
388            Element::Path(_) |
389            Element::Model(_) |
390            Element::TStamp(_) |
391            Element::SolderMaskMargin(_) |
392            Element::Clearance(_) |
393            Element::Tags(_) |
394            Element::Attr(_) |
395            Element::Locked => (),
396        }
397    }
398}
399
400impl Rotate for Element {
401    fn rotate(&mut self, rot: f64) {
402        match *self {
403            Element::Pad(ref mut p) => p.rotate(rot),
404            Element::FpText(ref mut p) => p.rotate(rot),
405            Element::At(ref mut p) => p.rotate(rot),
406            Element::FpPoly(_) |
407            Element::FpLine(_) |
408            Element::FpCircle(_) |
409            Element::FpArc(_) |
410            Element::Layer(_) |
411            Element::TEdit(_) |
412            Element::Descr(_) |
413            Element::Path(_) |
414            Element::Model(_) |
415            Element::TStamp(_) |
416            Element::SolderMaskMargin(_) |
417            Element::Clearance(_) |
418            Element::Tags(_) |
419            Element::Attr(_) |
420            Element::Locked => (),
421        }
422    }
423}
424
425impl Element {
426    fn is_graphics(&self) -> bool {
427        match *self {
428            Element::Pad(_) |
429            Element::FpPoly(_) |
430            Element::FpLine(_) |
431            Element::FpCircle(_) |
432            Element::FpArc(_) => true,
433            Element::FpText(_) |
434            Element::At(_) |
435            Element::Layer(_) |
436            Element::TEdit(_) |
437            Element::Descr(_) |
438            Element::Path(_) |
439            Element::Model(_) |
440            Element::TStamp(_) |
441            Element::SolderMaskMargin(_) |
442            Element::Clearance(_) |
443            Element::Tags(_) |
444            Element::Attr(_) |
445            Element::Locked => false,
446        }
447    }
448
449    fn is_fab(&self) -> bool {
450        match *self {
451            Element::FpPoly(ref e) => e.is_fab(),
452            Element::FpLine(ref e) => e.is_fab(),
453            Element::FpCircle(ref e) => e.is_fab(),
454            Element::FpArc(ref e) => e.is_fab(),
455            Element::Pad(_) |
456            Element::FpText(_) |
457            Element::At(_) |
458            Element::Layer(_) |
459            Element::TEdit(_) |
460            Element::Descr(_) |
461            Element::Path(_) |
462            Element::Model(_) |
463            Element::TStamp(_) |
464            Element::SolderMaskMargin(_) |
465            Element::Clearance(_) |
466            Element::Tags(_) |
467            Element::Attr(_) |
468            Element::Locked => false,
469        }
470    }
471
472    fn is_pad(&self) -> bool {
473        if let Element::Pad(_) = *self {
474            true
475        } else {
476            false
477        }
478    }
479}
480
481/// text element
482#[derive(Debug, Clone)]
483pub struct FpText {
484    /// name
485    pub name: String,
486    /// text
487    pub value: String,
488    /// location
489    pub at: At,
490    /// layer
491    pub layer: Layer,
492    /// text effects
493    pub effects: Effects,
494    /// is it a hidden text
495    pub hide: bool,
496}
497
498impl Flip for FpText {
499    fn flip(&mut self) {
500        self.at.flip();
501        self.layer.flip();
502        self.effects.flip();
503    }
504}
505
506impl Rotate for FpText {
507    fn rotate(&mut self, rot: f64) {
508        self.at.rotate(rot)
509    }
510}
511
512impl PartialEq for FpText {
513    fn eq(&self, other: &FpText) -> bool {
514        if self.name == "reference" && other.name == "reference" {
515            return true;
516        }
517        if self.name == "value" && other.name == "value" {
518            return true;
519        }
520        if self.at != other.at {
521            return false;
522        }
523        if self.name != other.name {
524            return false;
525        }
526        if self.value != other.value {
527            return false;
528        }
529        if self.at != other.at {
530            return false;
531        }
532        if self.layer != other.layer {
533            return false;
534        }
535        if self.effects != other.effects {
536            return false;
537        }
538        if self.hide != other.hide {
539            return false;
540        }
541        true
542    }
543}
544
545impl BoundingBox for FpText {
546    fn bounding_box(&self) -> Bound {
547        let (x, y) = (self.at.x, self.at.y);
548        debug!("bound for FpText is poor");
549        Bound::new(x, y, x, y)
550    }
551}
552
553impl FpText {
554    /// create a text with given name and value
555    pub fn new(name: String, value: String) -> FpText {
556        FpText {
557            name: name,
558            value: value,
559            at: At::default(),
560            layer: Layer::default(),
561            effects: Effects::default(),
562            hide: false,
563        }
564    }
565    /// set the text effects of the text
566    pub fn set_effects(&mut self, effects: &Effects) {
567        self.effects.clone_from(effects)
568    }
569    /// set the layer of the text
570    pub fn set_layer(&mut self, layer: &Layer) {
571        self.layer.clone_from(layer)
572    }
573}
574
575/// a location and rotation in a layout
576#[derive(Debug, Clone, Default, PartialEq)]
577pub struct At {
578    /// x coordinate
579    pub x: f64,
580    /// y coordinate
581    pub y: f64,
582    /// rotation
583    pub rot: f64,
584}
585
586impl Flip for At {
587    fn flip(&mut self) {
588        self.y = -self.y;
589        self.rot = 360.0 - self.rot;
590    }
591}
592
593impl Adjust for At {
594    fn adjust(&mut self, x: f64, y: f64) {
595        self.x += x;
596        self.y += y
597    }
598}
599
600impl At {
601    /// create a location
602    pub fn new(x: f64, y: f64, rot: f64) -> At {
603        At {
604            x: x,
605            y: y,
606            rot: rot,
607        }
608    }
609}
610
611impl Rotate for At {
612    fn rotate(&mut self, rot: f64) {
613        self.rot += rot;
614        if self.rot >= 360.0 {
615            self.rot -= 360.0
616        }
617        if self.rot < 0.0 {
618            self.rot += 360.0
619        }
620    }
621}
622
623/// font attributes for text
624#[derive(Debug, Clone, Default, PartialEq)]
625pub struct Font {
626    /// size of the font
627    pub size: Xy,
628    /// thickness of the font
629    pub thickness: f64,
630    /// if it is italic
631    pub italic: bool,
632}
633
634/// text effects
635#[derive(Debug, Clone, Default, PartialEq)]
636pub struct Effects {
637    /// the font used
638    pub font: Font,
639    /// the text justification
640    pub justify: Option<Justify>,
641}
642
643impl Flip for Effects {
644    fn flip(&mut self) {
645        self.justify = match self.justify {
646            None => Some(Justify::Mirror),
647            Some(_) => None,
648        }
649    }
650}
651
652impl Effects {
653    /// create a text effects element from font and justification
654    pub fn from_font(font: Font, justify: Option<Justify>) -> Effects {
655        Effects {
656            font: font,
657            justify: justify,
658        }
659    }
660}
661
662/// text justification
663#[derive(Debug, Clone, PartialEq)]
664pub enum Justify {
665    /// the text is mirrored
666    Mirror,
667    /// the text is left-justified
668    Left,
669    /// the text is right-justified
670    Right,
671}
672
673/// the type of X-Y element
674#[derive(Debug, Clone, PartialEq)]
675pub enum XyType {
676    /// regular
677    Xy,
678    /// starting point
679    Start,
680    /// ending point
681    End,
682    /// size
683    Size,
684    /// center point
685    Center,
686    /// rectangular delta
687    RectDelta,
688}
689
690impl Default for XyType {
691    fn default() -> XyType {
692        XyType::Xy
693    }
694}
695
696/// X-Y element
697#[derive(Debug, Clone, Default, PartialEq)]
698pub struct Xy {
699    /// x coordinate
700    pub x: f64,
701    /// y coorginate
702    pub y: f64,
703    /// the type of X-Y
704    pub t: XyType,
705}
706
707impl Flip for Xy {
708    fn flip(&mut self) {
709        self.y = -self.y;
710    }
711}
712
713impl Adjust for Xy {
714    fn adjust(&mut self, x: f64, y: f64) {
715        self.x += x;
716        self.y += y
717    }
718}
719
720impl Xy {
721    /// create a new X-Y coordinate
722    pub fn new(x: f64, y: f64, t: XyType) -> Xy {
723        Xy { x: x, y: y, t: t }
724    }
725    /// create a new default X-Y coordinate of a certain type
726    pub fn new_empty(t: XyType) -> Xy {
727        Xy {
728            x: 0.0,
729            y: 0.0,
730            t: t,
731        }
732    }
733}
734
735/// a list of X-Y coordinates
736#[derive(Debug, Clone, Default, PartialEq)]
737pub struct Pts {
738    /// the list of X-Y coordinates
739    pub elements: Vec<Xy>,
740}
741
742impl Flip for Pts {
743    fn flip(&mut self) {
744        for e in &mut self.elements {
745            e.flip()
746        }
747    }
748}
749
750impl Adjust for Pts {
751    fn adjust(&mut self, x: f64, y: f64) {
752        for e in &mut self.elements {
753            e.adjust(x, y)
754        }
755    }
756}
757
758impl BoundingBox for Pts {
759    fn bounding_box(&self) -> Bound {
760        let mut b = Bound::default();
761        for e in &self.elements {
762            let b2 = Bound::new(e.x, e.y, e.x, e.y);
763            b.update(&b2);
764        }
765        b.swap_if_needed();
766        b
767    }
768}
769
770/// a drill
771#[derive(Clone, Debug, Default, PartialEq)]
772pub struct Drill {
773    /// shape of the drill
774    pub shape: Option<String>,
775    /// width of the drill
776    pub width: f64,
777    /// height of the drill
778    pub height: f64,
779    /// x-offset of the drill
780    pub offset_x: f64,
781    /// y-offset of the drill
782    pub offset_y: f64,
783}
784
785/// type of a Pad
786#[derive(Debug, Clone, PartialEq)]
787pub enum PadType {
788    /// surface mount
789    Smd,
790    /// through-hole
791    Pth,
792    /// non-plated through-hole
793    NpPth,
794}
795
796impl PadType {
797    /// convert a &str to a pad type
798    pub fn from_string(s: &str) -> Result<PadType,SexpError> {
799        match s {
800            "smd" => Ok(PadType::Smd),
801            "thru_hole" => Ok(PadType::Pth),
802            "np_thru_hole" => Ok(PadType::NpPth),
803            x => Err(format!("unknown PadType {}", x).into()),
804        }
805    }
806}
807
808/// shape of a pad
809#[derive(Debug, Clone, PartialEq)]
810pub enum PadShape {
811    /// rectangular
812    Rect,
813    /// circular
814    Circle,
815    /// oval
816    Oval,
817    /// trapezoid
818    Trapezoid, // TODO
819}
820
821impl PadShape {
822    /// convert a &str to a pad shape
823    pub fn from_string(s: &str) -> Result<PadShape, SexpError> {
824        match s {
825            "rect" => Ok(PadShape::Rect),
826            "circle" => Ok(PadShape::Circle),
827            "oval" => Ok(PadShape::Oval),
828            "trapezoid" => Ok(PadShape::Trapezoid),
829            x => Err(format!("unknown PadShape: {}", x).into()),
830        }
831    }
832}
833
834/// side of a layer
835#[derive(Debug, Clone, PartialEq)]
836pub enum LayerSide {
837    /// front side
838    Front,
839    /// back side
840    Back,
841    /// Dwgs side
842    Dwgs,
843    /// Cmts side
844    Cmts,
845    /// Eco1 side
846    Eco1,
847    /// Eco2 side
848    Eco2,
849    /// edge of the board
850    Edge,
851    /// both sides
852    Both,
853    /// Inner layer 1
854    In1,
855    /// Inner layer 2
856    In2,
857    /// no side
858    None,
859}
860
861impl Flip for LayerSide {
862    fn flip(&mut self) {
863        let n = match *self {
864            LayerSide::Front => LayerSide::Back,
865            LayerSide::Back => LayerSide::Front,
866            ref x => x.clone(),
867        };
868        *self = n;
869    }
870}
871
872impl Default for LayerSide {
873    fn default() -> LayerSide {
874        LayerSide::Front
875    }
876}
877
878/// type of a layer
879#[derive(Debug, Clone, PartialEq)]
880pub enum LayerType {
881    /// copper layer
882    Cu,
883    /// paste layer
884    Paste,
885    /// solder mask layer
886    Mask,
887    /// silk screen layer
888    SilkS,
889    /// user layer
890    User,
891    /// adhesive layer
892    Adhes,
893    /// cuts layer
894    Cuts,
895    /// CrtYd layer
896    CrtYd,
897    /// fabrication layer
898    Fab,
899    /// margin layer
900    Margin,
901    /// an other custom named layer
902    Other(String),
903}
904
905impl Default for LayerType {
906    fn default() -> LayerType {
907        LayerType::Cu
908    }
909}
910
911/// a pcb layer, with a side and a type
912#[derive(Debug, Clone, Default)]
913pub struct Layer {
914    /// side of the layer
915    pub side: LayerSide,
916    /// type of the layer
917    pub t: LayerType,
918}
919
920impl Flip for Layer {
921    fn flip(&mut self) {
922        self.side.flip()
923    }
924}
925
926impl PartialEq for Layer {
927    fn eq(&self, other: &Layer) -> bool {
928        self.t == other.t
929    }
930}
931
932impl Layer {
933    /// create a layer from a String
934    pub fn from_string(s: &str) -> Result<Layer, SexpError> {
935        let sp: Vec<&str> = s.split('.').collect();
936        let mut side = LayerSide::None;
937        let s_t = if sp.len() == 2 {
938            side = match sp[0] {
939                "F" => LayerSide::Front,
940                "B" => LayerSide::Back,
941                "Dwgs" => LayerSide::Dwgs,
942                "Cmts" => LayerSide::Cmts,
943                "Eco1" => LayerSide::Eco1,
944                "Eco2" => LayerSide::Eco2,
945                "Edge" => LayerSide::Edge,
946                "In1" => LayerSide::In1,
947                "In2" => LayerSide::In2,
948                "*" => LayerSide::Both,
949                x => return Err(format!("unknown layer side {}", x).into()),
950            };
951            sp[1]
952        } else {
953            sp[0]
954        };
955
956        let t = match s_t {
957            "Cu" => LayerType::Cu,
958            "Paste" => LayerType::Paste,
959            "Mask" => LayerType::Mask,
960            "SilkS" => LayerType::SilkS,
961            "User" => LayerType::User,
962            "Adhes" => LayerType::Adhes,
963            "Cuts" => LayerType::Cuts,
964            "CrtYd" => LayerType::CrtYd,
965            "Fab" => LayerType::Fab,
966            "Margin" => LayerType::Margin,
967            x => LayerType::Other(String::from(x)),
968        };
969        Ok(Layer { side: side, t: t })
970    }
971}
972
973/// a list of layers
974#[derive(Debug, Clone, Default, PartialEq)]
975pub struct Layers {
976    /// a list of layers
977    pub layers: Vec<Layer>,
978}
979
980impl Flip for Layers {
981    fn flip(&mut self) {
982        for layer in &mut self.layers {
983            layer.flip()
984        }
985    }
986}
987
988impl Layers {
989    /// append a layer to a list of layers
990    pub fn append(&mut self, layer: Layer) {
991        self.layers.push(layer)
992    }
993}
994
995/// a pad
996#[derive(Debug, Clone)]
997pub struct Pad {
998    /// name
999    pub name: String,
1000    /// type
1001    pub t: PadType,
1002    /// shape
1003    pub shape: PadShape,
1004    /// size
1005    pub size: Xy,
1006    /// offset
1007    pub rect_delta: Option<Xy>,
1008    /// location
1009    pub at: At,
1010    /// layers
1011    pub layers: Layers,
1012    /// associated net
1013    pub net: Option<Net>,
1014    /// zone connect
1015    pub zone_connect: Option<i64>,
1016    /// drill
1017    pub drill: Option<Drill>,
1018    /// solder paste margin
1019    pub solder_paste_margin: Option<f64>,
1020    /// solder mask margin
1021    pub solder_mask_margin: Option<f64>,
1022    /// clearance
1023    pub clearance: Option<f64>,
1024    /// thermal gap
1025    pub thermal_gap: Option<f64>,
1026}
1027
1028impl Flip for Pad {
1029    fn flip(&mut self) {
1030        self.at.flip();
1031        self.layers.flip();
1032    }
1033}
1034
1035impl Rotate for Pad {
1036    fn rotate(&mut self, rot: f64) {
1037        self.at.rotate(rot)
1038    }
1039}
1040
1041impl PartialEq for Pad {
1042    fn eq(&self, other: &Pad) -> bool {
1043        if self.at != other.at {
1044            return false;
1045        }
1046        if self.name != other.name {
1047            return false;
1048        }
1049        if self.t != other.t {
1050            return false;
1051        }
1052        if self.shape != other.shape {
1053            return false;
1054        }
1055        if self.size != other.size {
1056            return false;
1057        }
1058        if self.rect_delta != other.rect_delta {
1059            return false;
1060        }
1061        if self.layers != other.layers {
1062            return false;
1063        }
1064        if self.zone_connect != other.zone_connect {
1065            return false;
1066        }
1067        if self.drill != other.drill {
1068            return false;
1069        }
1070        if self.solder_paste_margin != other.solder_paste_margin {
1071            return false;
1072        }
1073        if self.solder_mask_margin != other.solder_mask_margin {
1074            return false;
1075        }
1076        if self.clearance != other.clearance {
1077            return false;
1078        }
1079        if self.thermal_gap != other.thermal_gap {
1080            return false;
1081        }
1082        true
1083    }
1084}
1085
1086impl Pad {
1087    /// create a pad with a name, type and shape
1088    pub fn new(name: String, t: PadType, shape: PadShape) -> Pad {
1089        Pad {
1090            name: name,
1091            t: t,
1092            shape: shape,
1093            size: Xy::new_empty(XyType::Size),
1094            rect_delta: None,
1095            at: At::default(),
1096            layers: Layers::default(),
1097            net: None,
1098            zone_connect: None,
1099            drill: None,
1100            solder_paste_margin: None,
1101            solder_mask_margin: None,
1102            clearance: None,
1103            thermal_gap: None,
1104        }
1105    }
1106
1107    /// rename the net of a pad
1108    pub fn rename_net(&mut self, old_name: &str, new_name: &str) {
1109        let new_net = if let Some(ref net) = self.net {
1110            if net.name.0 == old_name {
1111                Some(Net {
1112                    name: new_name.into(),
1113                    ..*net
1114                })
1115            } else {
1116                Some(net.clone())
1117            }
1118        } else {
1119            None
1120        };
1121        self.net = new_net
1122    }
1123
1124    /// set the net of a pad
1125    pub fn set_net(&mut self, net: Net) {
1126        self.net = Some(net)
1127    }
1128
1129    /// set the drill of a pad
1130    pub fn set_drill(&mut self, drill: Drill) {
1131        self.drill = Some(drill)
1132    }
1133}
1134
1135impl BoundingBox for Pad {
1136    fn bounding_box(&self) -> Bound {
1137        let x = self.at.x;
1138        let y = self.at.y;
1139        let (dx, dy) = if (self.at.rot - 90.0).abs() < 0.1 || (self.at.rot + 90.0).abs() < 0.1
1140            || (self.at.rot - 270.0).abs() < 0.1
1141            || (self.at.rot + 270.0).abs() < 0.1
1142        {
1143            (self.size.y, self.size.x)
1144        } else {
1145            (self.size.x, self.size.y)
1146        };
1147        Bound::new(x - dx / 2.0, y - dy / 2.0, x + dx / 2.0, y + dy / 2.0)
1148    }
1149}
1150
1151/// a polygon
1152#[derive(Debug, Clone, Default, PartialEq)]
1153pub struct FpPoly {
1154    /// points
1155    pub pts: Pts,
1156    /// width
1157    pub width: f64,
1158    /// layer
1159    pub layer: Layer,
1160}
1161
1162impl Flip for FpPoly {
1163    fn flip(&mut self) {
1164        self.pts.flip();
1165        self.layer.flip()
1166    }
1167}
1168
1169impl BoundingBox for FpPoly {
1170    fn bounding_box(&self) -> Bound {
1171        let mut b = Bound::default();
1172        for p in &self.pts.elements {
1173            let b2 = Bound::new(p.x, p.y, p.x, p.y);
1174            b.update(&b2);
1175        }
1176        b.swap_if_needed();
1177        b
1178    }
1179}
1180
1181impl FpPoly {
1182    fn is_fab(&self) -> bool {
1183        self.layer.t == LayerType::Fab
1184    }
1185}
1186
1187/// a line
1188#[derive(Debug, Clone, PartialEq)]
1189pub struct FpLine {
1190    /// start point
1191    pub start: Xy,
1192    /// end point
1193    pub end: Xy,
1194    /// layer
1195    pub layer: Layer,
1196    /// width
1197    pub width: f64,
1198}
1199
1200impl Flip for FpLine {
1201    fn flip(&mut self) {
1202        self.start.flip();
1203        self.end.flip();
1204        self.layer.flip()
1205    }
1206}
1207
1208impl Default for FpLine {
1209    fn default() -> FpLine {
1210        FpLine {
1211            start: Xy::new_empty(XyType::Start),
1212            end: Xy::new_empty(XyType::End),
1213            layer: Layer::default(),
1214            width: 0.0,
1215        }
1216    }
1217}
1218
1219impl BoundingBox for FpLine {
1220    fn bounding_box(&self) -> Bound {
1221        Bound::new(self.start.x, self.start.y, self.end.x, self.end.y)
1222    }
1223}
1224
1225impl FpLine {
1226    fn make(x1: f64, y1: f64, x2: f64, y2: f64, t: LayerType, width: f64) -> FpLine {
1227        let mut line1 = FpLine::default();
1228        line1.start.x = x1;
1229        line1.start.y = y1;
1230        line1.end.x = x2;
1231        line1.end.y = y2;
1232        line1.layer.t = t;
1233        line1.width = width;
1234        line1
1235    }
1236
1237    fn is_fab(&self) -> bool {
1238        self.layer.t == LayerType::Fab
1239    }
1240}
1241
1242/// a circle
1243#[derive(Debug, Clone, PartialEq)]
1244pub struct FpCircle {
1245    /// center point
1246    pub center: Xy,
1247    /// end point
1248    pub end: Xy,
1249    /// layer
1250    pub layer: Layer,
1251    /// width
1252    pub width: f64,
1253}
1254
1255impl Default for FpCircle {
1256    fn default() -> FpCircle {
1257        FpCircle {
1258            center: Xy::new_empty(XyType::Center),
1259            end: Xy::new_empty(XyType::End),
1260            layer: Layer::default(),
1261            width: 0.0,
1262        }
1263    }
1264}
1265
1266impl Flip for FpCircle {
1267    fn flip(&mut self) {
1268        self.center.flip();
1269        self.end.flip();
1270        self.layer.flip()
1271    }
1272}
1273
1274impl BoundingBox for FpCircle {
1275    fn bounding_box(&self) -> Bound {
1276        let dx = self.center.x - self.end.x;
1277        let dy = self.center.y - self.end.y;
1278        let d2 = dx * dx + dy * dy;
1279        let d = d2.sqrt();
1280        Bound::new(
1281            self.center.x - d,
1282            self.center.y - d,
1283            self.center.x + d,
1284            self.center.y + d,
1285        )
1286    }
1287}
1288
1289impl FpCircle {
1290    fn is_fab(&self) -> bool {
1291        self.layer.t == LayerType::Fab
1292    }
1293}
1294
1295/// an arc
1296#[derive(Debug, Clone, PartialEq)]
1297pub struct FpArc {
1298    /// start point
1299    pub start: Xy,
1300    /// end point
1301    pub end: Xy,
1302    /// angle
1303    pub angle: f64,
1304    /// layer
1305    pub layer: Layer,
1306    /// width
1307    pub width: f64,
1308}
1309
1310impl Flip for FpArc {
1311    fn flip(&mut self) {
1312        self.start.flip();
1313        self.end.flip();
1314        self.layer.flip()
1315    }
1316}
1317
1318impl BoundingBox for FpArc {
1319    fn bounding_box(&self) -> Bound {
1320        // perhaps not correct
1321        Bound::new(self.start.x, self.start.y, self.end.x, self.end.y)
1322    }
1323}
1324
1325impl Default for FpArc {
1326    fn default() -> FpArc {
1327        FpArc {
1328            start: Xy::new_empty(XyType::Start),
1329            end: Xy::new_empty(XyType::End),
1330            angle: 0.0,
1331            layer: Layer::default(),
1332            width: 0.0,
1333        }
1334    }
1335}
1336
1337impl FpArc {
1338    fn is_fab(&self) -> bool {
1339        self.layer.t == LayerType::Fab
1340    }
1341}
1342
1343/// a net
1344#[derive(Debug, Clone, PartialEq)]
1345pub struct Net {
1346    /// net number
1347    pub num: i64,
1348    /// net name
1349    pub name: NetName,
1350}
1351
1352/// a 3D model
1353#[derive(Debug, Clone, PartialEq)]
1354pub struct Model {
1355    /// name
1356    pub name: String,
1357    /// location
1358    pub at: Xyz,
1359    /// scale
1360    pub scale: Xyz,
1361    /// rotation
1362    pub rotate: Xyz,
1363}
1364
1365/// a 3D X-Y-Z coordinate
1366#[derive(Debug, Clone, PartialEq)]
1367pub struct Xyz {
1368    /// X coordinate
1369    pub x: f64,
1370    /// Y coordinate
1371    pub y: f64,
1372    /// Z coordinate
1373    pub z: f64,
1374}
1375
1376impl Xyz {
1377    /// create a X-Y-Z coordinate
1378    pub fn new(x: f64, y: f64, z: f64) -> Xyz {
1379        Xyz { x: x, y: y, z: z }
1380    }
1381}
1382
1383impl CheckFix for Module {
1384    fn check(&self, config: &Config) -> Vec<CheckFixData> {
1385        let mut v = vec![];
1386        let name = &self.name;
1387        let font_size = config.m.font_size;
1388        let font_thickness = config.m.font_thickness;
1389
1390        if let Some(reference) = self.get_reference_text() {
1391            // 7.3 reference is correctly placed
1392            if reference.value.as_str() != "REF**" {
1393                v.push(CheckFixData::new(
1394                    7,
1395                    3,
1396                    name.clone(),
1397                    "reference should be REF**",
1398                ));
1399            }
1400            // 7.3 reference is on F.SilkS or B.SilkS
1401            if reference.layer.t != LayerType::SilkS {
1402                v.push(CheckFixData::new(
1403                    7,
1404                    3,
1405                    name.clone(),
1406                    "reference should be on SilkS layertype",
1407                ));
1408            }
1409            // 7.3 reference should not be hidden
1410            if reference.hide {
1411                v.push(CheckFixData::new(
1412                    7,
1413                    3,
1414                    name.clone(),
1415                    "reference should not be hidden",
1416                ));
1417            }
1418            // 7.3 aspect ratio should be 1:1
1419            if (reference.effects.font.size.x - reference.effects.font.size.y).abs() > EPSILON {
1420                v.push(CheckFixData::new(
1421                    7,
1422                    3,
1423                    name.clone(),
1424                    "reference label font aspect ratio should be 1:1",
1425                ));
1426            }
1427            // 7.3 font height should be 1.0
1428            // this is kind of big :(
1429            if (reference.effects.font.size.y - font_size).abs() > EPSILON {
1430                v.push(CheckFixData::info(
1431                    7,
1432                    3,
1433                    name.clone(),
1434                    format!("reference label should have height {}", font_size),
1435                ));
1436            }
1437            // 7.3 font width should be 1.0
1438            // this is kind of big :(
1439            if (reference.effects.font.size.x - font_size).abs() > EPSILON {
1440                v.push(CheckFixData::info(
1441                    7,
1442                    3,
1443                    name.clone(),
1444                    format!("reference label should have width {}", font_size),
1445                ));
1446            }
1447            // 7.4 font thickness should be 0.15
1448            if (reference.effects.font.thickness - font_thickness).abs() > EPSILON {
1449                v.push(CheckFixData::info(
1450                    7,
1451                    3,
1452                    name.clone(),
1453                    format!("reference label thickness should be {}", font_thickness),
1454                ))
1455            }
1456        // 7.3 TODO: further silkscreen checks
1457        // 7.3 TODO: check for intersection with pads etc
1458        } else {
1459            // 7.3 missing reference
1460            v.push(CheckFixData::new(7, 3, name.clone(), "reference missing"));
1461        }
1462
1463        if let Some(value) = self.get_value_text() {
1464            // 7.4 value text has to match value
1465            if value.value.as_str() != name.as_str() {
1466                v.push(CheckFixData::new(
1467                    7,
1468                    4,
1469                    name.clone(),
1470                    "value text has to match footprint name",
1471                ));
1472            }
1473            // 7.4 value has to be on F.Fab or B.Fab
1474            if value.layer.t != LayerType::Fab {
1475                v.push(CheckFixData::new(
1476                    7,
1477                    4,
1478                    name.clone(),
1479                    "value text has to be on Fab layer",
1480                ));
1481            }
1482            // 7.4 value should not be hidden
1483            if value.hide {
1484                v.push(CheckFixData::new(
1485                    7,
1486                    4,
1487                    name.clone(),
1488                    "value text should not be hidden",
1489                ));
1490            }
1491            // 7.4 font height should be 1.0
1492            // this is kind of big :(
1493            if (value.effects.font.size.y - font_size).abs() > EPSILON {
1494                v.push(CheckFixData::info(
1495                    7,
1496                    3,
1497                    name.clone(),
1498                    format!("value label should have height {}", font_size),
1499                ));
1500            }
1501            // 7.4 font width should be 1.0
1502            // this is kind of big :(
1503            if (value.effects.font.size.x - font_size).abs() > EPSILON {
1504                v.push(CheckFixData::info(
1505                    7,
1506                    3,
1507                    name.clone(),
1508                    format!("value label should have width {}", font_size),
1509                ));
1510            }
1511            // 7.4 font thickness should be 0.15
1512            if (value.effects.font.thickness - font_thickness).abs() > EPSILON {
1513                v.push(CheckFixData::info(
1514                    7,
1515                    3,
1516                    name.clone(),
1517                    format!("value label thickness should be {}", font_thickness),
1518                ))
1519            }
1520        // TODO
1521        } else {
1522            // 7.4 missing value
1523            v.push(CheckFixData::new(7, 4, name.clone(), "value missing"));
1524        }
1525
1526        if let Some(reference) = self.get_reference2_text() {
1527            // 7.4 reference 2 is correctly named
1528            if reference.value.as_str() != "%R" {
1529                v.push(CheckFixData::new(
1530                    7,
1531                    4,
1532                    name.clone(),
1533                    "reference 2 should be %R",
1534                ));
1535            }
1536            // 7.4 reference 2 is on F.Fab or B.Fab
1537            if reference.layer.t != LayerType::Fab {
1538                v.push(CheckFixData::new(
1539                    7,
1540                    4,
1541                    name.clone(),
1542                    "reference 2 should be on Fab layertype",
1543                ));
1544            }
1545            // 7.4 aspect ratio should be 1:1
1546            if (reference.effects.font.size.x - reference.effects.font.size.y).abs() > EPSILON {
1547                v.push(CheckFixData::new(
1548                    7,
1549                    4,
1550                    name.clone(),
1551                    "reference 2 label font aspect ratio should be 1:1",
1552                ));
1553            }
1554            // 7.4 font height should be 1.0
1555            // this is kind of big :(
1556            if (reference.effects.font.size.y - font_size).abs() > EPSILON {
1557                v.push(CheckFixData::info(
1558                    7,
1559                    3,
1560                    name.clone(),
1561                    format!("reference 2 label should have height {}", font_size),
1562                ));
1563            }
1564            // 7.4 font width should be 1.0
1565            // this is kind of big :(
1566            if (reference.effects.font.size.x - font_size).abs() > EPSILON {
1567                v.push(CheckFixData::info(
1568                    7,
1569                    3,
1570                    name.clone(),
1571                    format!("reference 2 label should have width {}", font_size),
1572                ));
1573            }
1574            // 7.4 font thickness should be 0.15
1575            if (reference.effects.font.thickness - font_thickness).abs() > EPSILON {
1576                v.push(CheckFixData::info(
1577                    7,
1578                    3,
1579                    name.clone(),
1580                    format!("reference 2 label thickness should be {}", font_thickness),
1581                ))
1582            }
1583        // 7.4 TODO: check for intersection with pads etc
1584        // 7.4 TODO: check Fab line widths
1585        } else {
1586            // 7.4 missing reference 2
1587            v.push(CheckFixData::new(7, 4, name.clone(), "reference 2 missing"));
1588        }
1589        // TODO 7.5 CrtYd checking
1590        // for now just check that there are 4 CrtYd lines
1591        let mut c = 0;
1592        for line in self.lines() {
1593            if line.layer.t == LayerType::CrtYd {
1594                c += 1;
1595            }
1596        }
1597        if c < 4 {
1598            v.push(CheckFixData::new(
1599                7,
1600                5,
1601                name.clone(),
1602                "missing courtyard on CrtYd layer",
1603            ));
1604        }
1605        // 8.1 For surface-mount devices, placement type must be set to "Surface Mount"
1606        let mut smd = 0;
1607        let mut pth = 0;
1608        for pad in self.pads() {
1609            if pad.t == PadType::Smd {
1610                smd += 1;
1611            } else if pad.t == PadType::Pth {
1612                pth += 1;
1613            }
1614        }
1615        if pth == 0 && smd > 0 {
1616            if !self.has_smd_attr() {
1617                v.push(CheckFixData::new(
1618                    8,
1619                    1,
1620                    name.clone(),
1621                    "SMD components need to have placement Smd (Normal+Insert in Properties)",
1622                ));
1623            }
1624        } else if pth > 0 && smd == 0 {
1625            // 9.1 For through-hole devices, placement type must be set to "Through Hole
1626            if self.has_smd_attr() {
1627                v.push(CheckFixData::new(
1628                    9,
1629                    1,
1630                    name.clone(),
1631                    "SMD components need to have placement Smd (Normal+Insert in Properties)",
1632                ));
1633            }
1634            // TODO 9.2 For through-hole components, footprint anchor is set on pad 1
1635            // TODO 9.4 Layer requirements
1636            // TODO 9.5 Minimum drilled hole diameter is the maximum lead diameter plus 0.20mm (IPC-2222 Class 2)
1637            // TODO 9.6 Minimum annular ring
1638        }
1639        // TODO 8.2 For surface-mount devices, footprint anchor is placed in the middle of the footprint (IPC-7351).
1640        // TODO 8.3 SMD pad layer requirements
1641        // TODO 10.1 Footprint name must match its filename. (.kicad_mod files)
1642        // TODO 10.2 description and keyword tags
1643        // TODO 10.3 all other fields are at default
1644        // TODO 10.4 3D model reference
1645        v
1646    }
1647
1648    fn fix(&mut self, config: &Config) {
1649        let name = self.name.clone();
1650        // fix reference
1651        if let Some(reference) = self.get_reference_text_mut() {
1652            reference.value.clear();
1653            reference.value.push_str("REF**");
1654            reference.layer.t = LayerType::SilkS;
1655            reference.hide = false;
1656            reference.effects.font.size.x = config.m.font_size;
1657            reference.effects.font.size.y = config.m.font_size;
1658            reference.effects.font.thickness = config.m.font_thickness;
1659        } else {
1660            // reference should always be there
1661        }
1662        // fix value
1663        if let Some(value) = self.get_value_text_mut() {
1664            value.value.clear();
1665            value.value.push_str(&name);
1666            value.layer.t = LayerType::Fab;
1667            value.hide = false;
1668            value.effects.font.size.x = config.m.font_size;
1669            value.effects.font.size.y = config.m.font_size;
1670            value.effects.font.thickness = config.m.font_thickness;
1671        } else {
1672            // value should always be there
1673        }
1674        // fix reference2
1675        {
1676            if self.get_reference2_text().is_none() {
1677                let size = Xy {
1678                    x: config.m.font_size,
1679                    y: config.m.font_size,
1680                    t: XyType::default(),
1681                };
1682                let font = Font {
1683                    size: size,
1684                    thickness: config.m.font_thickness,
1685                    italic: false,
1686                };
1687                let ref2 = FpText {
1688                    name: "user".into(),
1689                    value: "%R".into(),
1690                    at: At::new(0.0, 0.0, 0.0),
1691                    layer: Layer::from_string("F.Fab").unwrap(),
1692                    effects: Effects::from_font(font, None),
1693                    hide: false,
1694                };
1695                self.elements.push(Element::FpText(ref2));
1696            };
1697            let ref2 = self.get_reference2_text_mut().unwrap();
1698            ref2.value.clear();
1699            ref2.value.push_str("%R");
1700            ref2.layer.t = LayerType::Fab;
1701            ref2.hide = false;
1702            ref2.effects.font.size.x = config.m.font_size;
1703            ref2.effects.font.size.y = config.m.font_size;
1704            ref2.effects.font.thickness = config.m.font_thickness;
1705        }
1706        // set Surface Mount placement for SMD components
1707        {
1708            let mut smd = 0;
1709            let mut pth = 0;
1710            for pad in self.pads() {
1711                if pad.t == PadType::Smd {
1712                    smd += 1;
1713                } else if pad.t == PadType::Pth {
1714                    pth += 1;
1715                }
1716            }
1717            if pth == 0 && smd > 0 {
1718                if !self.has_smd_attr() {
1719                    self.elements.push(Element::Attr("smd".into()))
1720                }
1721            }
1722        }
1723        // Generate CrtYd if none found
1724        let mut c = 0;
1725        for line in self.lines() {
1726            if line.layer.t == LayerType::CrtYd {
1727                c += 1;
1728            }
1729        }
1730        if c < 4 {
1731            // this is not perfect of course...
1732            let bound = self.bounding_box();
1733            debug!("bound: {:?}", bound);
1734            let (x, y) = self.at();
1735            debug!("at: {:?}", (x, y));
1736            let offset = if bound.width() < 2.0 && bound.height() < 2.0 {
1737                0.15
1738            } else if self.name.starts_with("BGA") {
1739                0.1
1740            } else {
1741                0.25
1742            };
1743            // make relative to center of module and add offset
1744            let x1a = bound.x1 - x;
1745            let y1a = bound.y1 - y;
1746            let x2a = bound.x2 - x;
1747            let y2a = bound.y2 - y;
1748            debug!("a: {:?}", ((x1a, y1a), (x2a, y2a)));
1749            let x1 = x1a.min(x2a) - offset;
1750            let y1 = y1a.min(y2a) - offset;
1751            let x2 = x1a.max(x2a) + offset;
1752            let y2 = y1a.max(y2a) + offset;
1753            info!("Creating courtyard: {},{} -> {},{}", x1, y1, x2, y2);
1754            let line1 = FpLine::make(x1, y1, x2, y1, LayerType::CrtYd, 0.05);
1755            let line2 = FpLine::make(x2, y1, x2, y2, LayerType::CrtYd, 0.05);
1756            let line3 = FpLine::make(x2, y2, x1, y2, LayerType::CrtYd, 0.05);
1757            let line4 = FpLine::make(x1, y2, x1, y1, LayerType::CrtYd, 0.05);
1758            self.elements.push(Element::FpLine(line1));
1759            self.elements.push(Element::FpLine(line2));
1760            self.elements.push(Element::FpLine(line3));
1761            self.elements.push(Element::FpLine(line4));
1762        }
1763    }
1764}
1765
1766#[cfg(test)]
1767mod test {
1768    use std::f64::EPSILON;
1769
1770    use super::*;
1771    #[test]
1772    fn pad_bound() {
1773        let mut pad = Pad::new("hello".into(), PadType::Smd, PadShape::Rect);
1774        pad.at.x = 1.4;
1775        pad.at.y = 0.95;
1776        pad.at.rot = 0.0;
1777        pad.size.x = 1.2;
1778        pad.size.y = 0.6;
1779        let bound = pad.bounding_box();
1780        println!("bound: {:?}", bound);
1781        assert!((bound.x2 - pad.at.x - pad.size.x / 2.0).abs() < EPSILON);
1782        assert!((bound.y2 - pad.at.y - pad.size.y / 2.0).abs() < EPSILON);
1783        assert!((bound.x1 - pad.at.x + pad.size.x / 2.0).abs() < EPSILON);
1784        assert!((bound.y1 - pad.at.y + pad.size.y / 2.0).abs() < EPSILON);
1785    }
1786
1787    #[test]
1788    fn pad_bound_180() {
1789        let mut pad = Pad::new("hello".into(), PadType::Smd, PadShape::Rect);
1790        pad.at.x = 1.4;
1791        pad.at.y = 0.95;
1792        pad.at.rot = 180.0;
1793        pad.size.x = 1.2;
1794        pad.size.y = 0.6;
1795        let bound = pad.bounding_box();
1796        println!("180 bound: {:?}", bound);
1797        assert!((bound.x2 - pad.at.x - pad.size.x / 2.0).abs() < EPSILON);
1798        assert!((bound.y2 - pad.at.y - pad.size.y / 2.0).abs() < EPSILON);
1799        assert!((bound.x1 - pad.at.x + pad.size.x / 2.0).abs() < EPSILON);
1800        assert!((bound.y1 - pad.at.y + pad.size.y / 2.0).abs() < EPSILON);
1801    }
1802
1803    #[test]
1804    fn bound_fail_cy() {
1805        let cy = include_str!("../../tests/data/cy.kicad_mod");
1806        let module = ::footprint::parse(cy).unwrap();
1807        let bound = module.bounding_box();
1808        println!("cy bound: {:?}", bound);
1809        assert!((bound.x2 - 2.95).abs() < EPSILON);
1810        assert!((bound.y2 - 2.95).abs() < EPSILON);
1811        assert!((bound.x1 + 2.95).abs() < EPSILON);
1812        assert!((bound.y1 + 2.95).abs() < EPSILON);
1813    }
1814}