1use super::FootprintBuilder;
4use crate::records::pcb::PcbPadShape;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum PackageType {
9 Chip,
11 Sot,
13 Sop,
15 Soic,
17 Tssop,
19 Qfp,
21 Qfn,
23 Bga,
25 Dip,
27 Pga,
29 Connector,
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub enum LeadStyle {
36 GullWing,
38 JLead,
40 Flat,
42 Ball,
44 ThroughHole,
46 Chip,
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub enum IpcDensity {
53 MostDense,
55 Nominal,
57 LeastDense,
59}
60
61impl IpcDensity {
62 pub fn courtyard_excess_mm(&self) -> f64 {
64 match self {
65 IpcDensity::MostDense => 0.10,
66 IpcDensity::Nominal => 0.25,
67 IpcDensity::LeastDense => 0.50,
68 }
69 }
70
71 pub fn toe_fillet_mm(&self, pitch_mm: f64) -> f64 {
73 if pitch_mm <= 0.625 {
74 match self {
75 IpcDensity::MostDense => 0.15,
76 IpcDensity::Nominal => 0.35,
77 IpcDensity::LeastDense => 0.55,
78 }
79 } else {
80 match self {
81 IpcDensity::MostDense => 0.25,
82 IpcDensity::Nominal => 0.45,
83 IpcDensity::LeastDense => 0.65,
84 }
85 }
86 }
87
88 pub fn heel_fillet_mm(&self, pitch_mm: f64) -> f64 {
90 if pitch_mm <= 0.625 {
91 match self {
92 IpcDensity::MostDense => 0.25,
93 IpcDensity::Nominal => 0.35,
94 IpcDensity::LeastDense => 0.45,
95 }
96 } else {
97 match self {
98 IpcDensity::MostDense => 0.35,
99 IpcDensity::Nominal => 0.45,
100 IpcDensity::LeastDense => 0.55,
101 }
102 }
103 }
104
105 pub fn side_fillet_mm(&self, pitch_mm: f64) -> f64 {
107 if pitch_mm <= 0.625 {
108 match self {
109 IpcDensity::MostDense => -0.02,
110 IpcDensity::Nominal => 0.01,
111 IpcDensity::LeastDense => 0.05,
112 }
113 } else {
114 match self {
115 IpcDensity::MostDense => 0.01,
116 IpcDensity::Nominal => 0.05,
117 IpcDensity::LeastDense => 0.07,
118 }
119 }
120 }
121}
122
123#[derive(Debug, Clone)]
125pub struct ChipSpec {
126 pub size_code: String,
128 pub body_length_mm: f64,
130 pub body_width_mm: f64,
132 pub terminal_length_mm: f64,
134 pub height_mm: f64,
136}
137
138impl ChipSpec {
139 pub fn chip_0201() -> Self {
141 Self {
142 size_code: "0201".to_string(),
143 body_length_mm: 0.60,
144 body_width_mm: 0.30,
145 terminal_length_mm: 0.15,
146 height_mm: 0.30,
147 }
148 }
149
150 pub fn chip_0402() -> Self {
152 Self {
153 size_code: "0402".to_string(),
154 body_length_mm: 1.00,
155 body_width_mm: 0.50,
156 terminal_length_mm: 0.25,
157 height_mm: 0.50,
158 }
159 }
160
161 pub fn chip_0603() -> Self {
163 Self {
164 size_code: "0603".to_string(),
165 body_length_mm: 1.60,
166 body_width_mm: 0.80,
167 terminal_length_mm: 0.30,
168 height_mm: 0.80,
169 }
170 }
171
172 pub fn chip_0805() -> Self {
174 Self {
175 size_code: "0805".to_string(),
176 body_length_mm: 2.00,
177 body_width_mm: 1.25,
178 terminal_length_mm: 0.40,
179 height_mm: 1.00,
180 }
181 }
182
183 pub fn chip_1206() -> Self {
185 Self {
186 size_code: "1206".to_string(),
187 body_length_mm: 3.20,
188 body_width_mm: 1.60,
189 terminal_length_mm: 0.50,
190 height_mm: 1.10,
191 }
192 }
193
194 pub fn to_footprint(&self, density: IpcDensity) -> FootprintBuilder {
196 let toe = 0.55; let heel = 0.00; let side = 0.05; let courtyard_excess = density.courtyard_excess_mm();
202
203 let pad_length = self.terminal_length_mm + toe - heel;
205 let pad_width = self.body_width_mm + 2.0 * side;
206
207 let pad_center_x = (self.body_length_mm - self.terminal_length_mm + pad_length) / 2.0;
209
210 let mut builder = FootprintBuilder::new(format!("CHIP_{}", self.size_code))
211 .description(format!("{} Chip Component", self.size_code))
212 .height_mm(self.height_mm);
213
214 builder.add_smd_pad(
216 "1",
217 -pad_center_x,
218 0.0,
219 pad_length,
220 pad_width,
221 PcbPadShape::Rectangular,
222 );
223 builder.add_smd_pad(
224 "2",
225 pad_center_x,
226 0.0,
227 pad_length,
228 pad_width,
229 PcbPadShape::Rectangular,
230 );
231
232 let silk_width = 0.15;
234 let silk_y = pad_width / 2.0 + silk_width;
235 let silk_x = self.body_length_mm / 2.0;
236
237 builder.add_silkscreen_line(-silk_x, silk_y, silk_x, silk_y, silk_width);
238 builder.add_silkscreen_line(-silk_x, -silk_y, silk_x, -silk_y, silk_width);
239
240 let cy_x = pad_center_x + pad_length / 2.0 + courtyard_excess;
242 let cy_y = pad_width / 2.0 + courtyard_excess;
243 builder.add_courtyard_rect(0.0, 0.0, 2.0 * cy_x, 2.0 * cy_y, 0.05);
244
245 builder
246 }
247}
248
249#[derive(Debug, Clone)]
251pub struct GullWingSpec {
252 pub name: String,
254 pub pin_count: u32,
256 pub pitch_mm: f64,
258 pub lead_span_mm: f64,
260 pub lead_width_mm: f64,
262 pub lead_length_mm: f64,
264 pub body_width_mm: f64,
266 pub body_length_mm: f64,
268 pub height_mm: f64,
270 pub sides: u8,
272}
273
274impl GullWingSpec {
275 pub fn soic_8() -> Self {
277 Self {
278 name: "SOIC-8".to_string(),
279 pin_count: 8,
280 pitch_mm: 1.27,
281 lead_span_mm: 6.0,
282 lead_width_mm: 0.40,
283 lead_length_mm: 0.70,
284 body_width_mm: 3.9,
285 body_length_mm: 4.9,
286 height_mm: 1.75,
287 sides: 2,
288 }
289 }
290
291 pub fn tssop_20() -> Self {
293 Self {
294 name: "TSSOP-20".to_string(),
295 pin_count: 20,
296 pitch_mm: 0.65,
297 lead_span_mm: 6.4,
298 lead_width_mm: 0.25,
299 lead_length_mm: 0.60,
300 body_width_mm: 4.4,
301 body_length_mm: 6.5,
302 height_mm: 1.1,
303 sides: 2,
304 }
305 }
306
307 pub fn lqfp_48() -> Self {
309 Self {
310 name: "LQFP-48".to_string(),
311 pin_count: 48,
312 pitch_mm: 0.5,
313 lead_span_mm: 9.0,
314 lead_width_mm: 0.22,
315 lead_length_mm: 0.50,
316 body_width_mm: 7.0,
317 body_length_mm: 7.0,
318 height_mm: 1.4,
319 sides: 4,
320 }
321 }
322
323 pub fn to_footprint(&self, density: IpcDensity) -> FootprintBuilder {
325 let toe = density.toe_fillet_mm(self.pitch_mm);
326 let heel = density.heel_fillet_mm(self.pitch_mm);
327 let side = density.side_fillet_mm(self.pitch_mm);
328 let courtyard_excess = density.courtyard_excess_mm();
329
330 let pad_length = self.lead_length_mm + toe + heel;
332 let pad_width = self.lead_width_mm + 2.0 * side;
333
334 let pad_center = (self.lead_span_mm - self.lead_length_mm + pad_length) / 2.0;
336
337 let mut builder = FootprintBuilder::new(&self.name)
338 .description(format!("{} package", self.name))
339 .height_mm(self.height_mm);
340
341 if self.sides == 2 {
342 let pins_per_side = self.pin_count / 2;
344 let total_span = (pins_per_side - 1) as f64 * self.pitch_mm;
345 let start_y = -total_span / 2.0;
346
347 for i in 0..pins_per_side {
349 let y = start_y + i as f64 * self.pitch_mm;
350 builder.add_smd_pad(
351 (i + 1).to_string(),
352 -pad_center,
353 y,
354 pad_length,
355 pad_width,
356 PcbPadShape::Rectangular,
357 );
358 }
359
360 for i in 0..pins_per_side {
362 let y = -start_y - i as f64 * self.pitch_mm;
363 builder.add_smd_pad(
364 (pins_per_side + i + 1).to_string(),
365 pad_center,
366 y,
367 pad_length,
368 pad_width,
369 PcbPadShape::Rectangular,
370 );
371 }
372 } else {
373 let pins_per_side = self.pin_count / 4;
375 let total_span = (pins_per_side - 1) as f64 * self.pitch_mm;
376 let start_pos = -total_span / 2.0;
377
378 for i in 0..pins_per_side {
380 let x = start_pos + i as f64 * self.pitch_mm;
381 builder.add_smd_pad(
382 (i + 1).to_string(),
383 x,
384 -pad_center,
385 pad_width,
386 pad_length,
387 PcbPadShape::Rectangular,
388 );
389 }
390
391 for i in 0..pins_per_side {
393 let y = start_pos + i as f64 * self.pitch_mm;
394 builder.add_smd_pad(
395 (pins_per_side + i + 1).to_string(),
396 pad_center,
397 y,
398 pad_length,
399 pad_width,
400 PcbPadShape::Rectangular,
401 );
402 }
403
404 for i in 0..pins_per_side {
406 let x = -start_pos - i as f64 * self.pitch_mm;
407 builder.add_smd_pad(
408 (2 * pins_per_side + i + 1).to_string(),
409 x,
410 pad_center,
411 pad_width,
412 pad_length,
413 PcbPadShape::Rectangular,
414 );
415 }
416
417 for i in 0..pins_per_side {
419 let y = -start_pos - i as f64 * self.pitch_mm;
420 builder.add_smd_pad(
421 (3 * pins_per_side + i + 1).to_string(),
422 -pad_center,
423 y,
424 pad_length,
425 pad_width,
426 PcbPadShape::Rectangular,
427 );
428 }
429 }
430
431 let silk_width = 0.15;
433 let body_half_w = self.body_width_mm / 2.0;
434 let body_half_l = self.body_length_mm / 2.0;
435
436 if self.sides == 2 {
438 builder.add_silkscreen_line(
439 -body_half_w,
440 body_half_l,
441 body_half_w,
442 body_half_l,
443 silk_width,
444 );
445 builder.add_silkscreen_line(
446 -body_half_w,
447 -body_half_l,
448 body_half_w,
449 -body_half_l,
450 silk_width,
451 );
452 }
453
454 let pin1_x = if self.sides == 2 {
456 -pad_center - pad_length
457 } else {
458 0.0
459 };
460 let pin1_y = if self.sides == 2 {
461 -(self.pin_count as f64 / 4.0 - 0.5) * self.pitch_mm
462 } else {
463 -pad_center - pad_length
464 };
465 builder.add_pin1_indicator(pin1_x - 0.3, pin1_y - 0.3, 0.3);
466
467 let cy_extent = pad_center + pad_length / 2.0 + courtyard_excess;
469 builder.add_courtyard_rect(0.0, 0.0, 2.0 * cy_extent, 2.0 * cy_extent, 0.05);
470
471 builder
472 }
473}
474
475#[cfg(test)]
476mod tests {
477 use super::*;
478
479 #[test]
480 fn test_chip_0805_footprint() {
481 let mut det = ();
482 let spec = ChipSpec::chip_0805();
483 let footprint = spec
484 .to_footprint(IpcDensity::Nominal)
485 .build_deterministic(&mut det);
486
487 assert_eq!(footprint.pad_count(), 2);
488 assert!(footprint.pattern.contains("0805"));
489 }
490
491 #[test]
492 fn test_soic8_footprint() {
493 let mut det = ();
494 let spec = GullWingSpec::soic_8();
495 let footprint = spec
496 .to_footprint(IpcDensity::Nominal)
497 .build_deterministic(&mut det);
498
499 assert_eq!(footprint.pad_count(), 8);
500 assert!(footprint.pattern.contains("SOIC"));
501 }
502}