1#[derive(Debug, PartialEq, Eq)]
11pub struct ValidationReport {
12 pub layer_count: u32,
13 pub warnings: Vec<String>,
14}
15
16const REQUIRED_PREFIXES: &[&str] = &[
18 "Gerber_BoardOutlineLayer",
19 "Gerber_TopLayer",
20 "Gerber_TopSolderMaskLayer",
21];
22
23pub fn validate_gerber_files(files: &[String]) -> Result<ValidationReport, Vec<String>> {
39 let mut errors: Vec<String> = Vec::new();
40 let mut warnings: Vec<String> = Vec::new();
41
42 for &prefix in REQUIRED_PREFIXES {
44 if !files.iter().any(|f| f.starts_with(prefix)) {
45 errors.push(format!("Missing required file starting with: '{}'", prefix));
46 }
47 }
48
49 let has_top_copper = files.iter().any(|f| f.starts_with("Gerber_TopLayer"));
51 let has_bottom_copper = files.iter().any(|f| f.starts_with("Gerber_BottomLayer"));
52 let inner_layer_count = files
53 .iter()
54 .filter(|f| f.starts_with("Gerber_InnerLayer"))
55 .count() as u32;
56
57 if has_top_copper {
61 if !files
62 .iter()
63 .any(|f| f.starts_with("Gerber_TopSolderMaskLayer"))
64 {
65 if !errors
68 .iter()
69 .any(|e| e.contains("Gerber_TopSolderMaskLayer"))
70 {
71 errors.push("Top copper layer is present, but the required 'Gerber_TopSolderMaskLayer' is missing.".to_string());
72 }
73 }
74 if !files
75 .iter()
76 .any(|f| f.starts_with("Gerber_TopSilkscreenLayer"))
77 {
78 warnings.push(
79 "Warning: 'Gerber_TopSilkscreenLayer' is missing for the top side.".to_string(),
80 );
81 }
82 if !files
83 .iter()
84 .any(|f| f.starts_with("Gerber_TopPasteMaskLayer"))
85 {
86 warnings.push("Warning: 'Gerber_TopPasteMaskLayer' is missing. This is usually needed for SMD components.".to_string());
87 }
88 }
89
90 if has_bottom_copper {
92 if !files
93 .iter()
94 .any(|f| f.starts_with("Gerber_BottomSolderMaskLayer"))
95 {
96 errors.push(
97 "Bottom copper layer is present, but 'Gerber_BottomSolderMaskLayer' is missing."
98 .to_string(),
99 );
100 }
101 if !files
102 .iter()
103 .any(|f| f.starts_with("Gerber_BottomSilkscreenLayer"))
104 {
105 warnings.push(
106 "Warning: 'Gerber_BottomSilkscreenLayer' is missing for the bottom side."
107 .to_string(),
108 );
109 }
110 if !files
111 .iter()
112 .any(|f| f.starts_with("Gerber_BottomPasteMaskLayer"))
113 {
114 warnings.push(
115 "Warning: 'Gerber_BottomPasteMaskLayer' is missing for the bottom side."
116 .to_string(),
117 );
118 }
119 }
120
121 if has_top_copper && inner_layer_count > 0 && !has_bottom_copper {
123 errors.push("Invalid layer stackup: A board with top and inner copper layers must also have a bottom copper layer.".to_string());
124 }
125
126 let total_layer_count =
128 (has_top_copper as u32) + (has_bottom_copper as u32) + inner_layer_count;
129
130 if errors.is_empty() {
132 Ok(ValidationReport {
133 layer_count: total_layer_count,
134 warnings,
135 })
136 } else {
137 Err(errors)
138 }
139}