Skip to main content

altium_format/footprint/
package.rs

1//! Package type definitions and IPC land pattern calculations.
2
3use super::FootprintBuilder;
4use crate::records::pcb::PcbPadShape;
5
6/// Common package types.
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum PackageType {
9    /// Chip resistor/capacitor (0201, 0402, 0603, etc.)
10    Chip,
11    /// Small Outline Transistor
12    Sot,
13    /// Small Outline Package
14    Sop,
15    /// Small Outline Integrated Circuit
16    Soic,
17    /// Thin Small Outline Package
18    Tssop,
19    /// Quad Flat Package
20    Qfp,
21    /// Quad Flat No-lead
22    Qfn,
23    /// Ball Grid Array
24    Bga,
25    /// Dual In-line Package
26    Dip,
27    /// Pin Grid Array
28    Pga,
29    /// Through-Hole connector
30    Connector,
31}
32
33/// Lead style for packages.
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub enum LeadStyle {
36    /// Gull-wing leads (SOIC, QFP, TSSOP)
37    GullWing,
38    /// J-lead (PLCC)
39    JLead,
40    /// No-lead / Flat lead (QFN, DFN)
41    Flat,
42    /// Ball (BGA)
43    Ball,
44    /// Through-hole
45    ThroughHole,
46    /// Chip termination
47    Chip,
48}
49
50/// IPC-7351 density levels.
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub enum IpcDensity {
53    /// Most Dense (Least) - Level A
54    MostDense,
55    /// Nominal (Normal) - Level B
56    Nominal,
57    /// Least Dense (Most) - Level C
58    LeastDense,
59}
60
61impl IpcDensity {
62    /// Get courtyard excess for this density level.
63    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    /// Get toe fillet for gull-wing leads.
72    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    /// Get heel fillet for gull-wing leads.
89    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    /// Get side fillet for gull-wing leads.
106    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/// Chip component specification.
124#[derive(Debug, Clone)]
125pub struct ChipSpec {
126    /// Imperial size code (e.g., "0402", "0603", "0805")
127    pub size_code: String,
128    /// Body length (mm)
129    pub body_length_mm: f64,
130    /// Body width (mm)
131    pub body_width_mm: f64,
132    /// Terminal length (mm)
133    pub terminal_length_mm: f64,
134    /// Component height (mm)
135    pub height_mm: f64,
136}
137
138impl ChipSpec {
139    /// Standard 0201 chip.
140    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    /// Standard 0402 chip.
151    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    /// Standard 0603 chip.
162    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    /// Standard 0805 chip.
173    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    /// Standard 1206 chip.
184    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    /// Create a footprint for this chip spec.
195    pub fn to_footprint(&self, density: IpcDensity) -> FootprintBuilder {
196        // IPC-7351B calculations for chip components
197        let toe = 0.55; // Toe extension
198        let heel = 0.00; // Heel extension (negative for chips)
199        let side = 0.05; // Side extension
200
201        let courtyard_excess = density.courtyard_excess_mm();
202
203        // Calculate pad dimensions
204        let pad_length = self.terminal_length_mm + toe - heel;
205        let pad_width = self.body_width_mm + 2.0 * side;
206
207        // Calculate pad center position
208        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        // Add pads
215        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        // Add silkscreen (avoid pads)
233        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        // Add courtyard
241        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/// Gull-wing IC specification (SOIC, TSSOP, QFP).
250#[derive(Debug, Clone)]
251pub struct GullWingSpec {
252    /// Package name (e.g., "SOIC-8", "TSSOP-20")
253    pub name: String,
254    /// Number of pins.
255    pub pin_count: u32,
256    /// Pitch between pins (mm).
257    pub pitch_mm: f64,
258    /// Lead span (tip to tip, mm).
259    pub lead_span_mm: f64,
260    /// Lead width (mm).
261    pub lead_width_mm: f64,
262    /// Lead length (mm).
263    pub lead_length_mm: f64,
264    /// Body width (mm) - parallel to leads.
265    pub body_width_mm: f64,
266    /// Body length (mm) - perpendicular to leads.
267    pub body_length_mm: f64,
268    /// Component height (mm).
269    pub height_mm: f64,
270    /// Number of sides with pins (2 for SOIC, 4 for QFP).
271    pub sides: u8,
272}
273
274impl GullWingSpec {
275    /// Standard SOIC-8 narrow body.
276    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    /// Standard TSSOP-20.
292    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    /// Standard LQFP-48.
308    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    /// Create a footprint for this IC spec.
324    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        // Calculate pad dimensions
331        let pad_length = self.lead_length_mm + toe + heel;
332        let pad_width = self.lead_width_mm + 2.0 * side;
333
334        // Pad center distance from origin
335        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            // Two-sided package (SOIC, TSSOP)
343            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            // Left side (pins 1 to N/2)
348            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            // Right side (pins N/2+1 to N, going down)
361            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            // Four-sided package (QFP)
374            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            // Bottom side (starting from pin 1)
379            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            // Right side
392            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            // Top side (going right to left)
405            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            // Left side (going down)
418            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        // Add silkscreen (body outline, avoiding pads)
432        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        // For two-sided, draw top and bottom lines
437        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        // Pin 1 indicator
455        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        // Courtyard
468        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}