diff --git a/src/fn/sot457.ts b/src/fn/sot457.ts index 18937b3e..ed4ca7f3 100644 --- a/src/fn/sot457.ts +++ b/src/fn/sot457.ts @@ -175,7 +175,12 @@ const generateSot457Elements = ( const pin1Position = getCcwSot457Coords({ pitch, width, pinNumber: 1 }) const triangleHeight = params.wave ? 1 : 0.5 const triangleWidth = params.wave ? 0.7 : 0.3 - pin1Position.x -= params.wave ? padWidth : padWidth * 1.7 + // Place the marker outboard of pad 1's actual edge. Offsetting by a multiple + // of the pad *width* instead let the triangle land on the pad whenever pl and + // pw are set independently (e.g. `sot457_w2_pl1.5_pw0.6`, where it overlapped + // by 0.005mm). + const pin1PadOuterEdgeX = pin1Position.x - padLength / 2 + pin1Position.x = pin1PadOuterEdgeX - triangleHeight / 2 - 0.2 - 0.05 / 2 const pin1Indicator: PcbSilkscreenPath = { type: "pcb_silkscreen_path", diff --git a/src/fn/tssop.ts b/src/fn/tssop.ts index bd2d67e9..3a21c58b 100644 --- a/src/fn/tssop.ts +++ b/src/fn/tssop.ts @@ -66,6 +66,9 @@ export const tssop = ( ? parameters.w - length.parse("0.15mm") : parameters.w + /** Distance from the origin to the inner edge of the pad columns. */ + let padInnerHalfX = Number.POSITIVE_INFINITY + let padOuterHalfY = 0 for (let i = 0; i < parameters.num_pins; i++) { const { x, y } = getTssopCoords({ num_pins: parameters.num_pins, @@ -75,6 +78,8 @@ export const tssop = ( pl: parameters.pl, legsoutside: parameters.legsoutside, }) + padInnerHalfX = Math.min(padInnerHalfX, Math.abs(x) - parameters.pl / 2) + padOuterHalfY = Math.max(padOuterHalfY, Math.abs(y) + parameters.pw / 2) pads.push(rectpad(i + 1, x, y, parameters.pl, parameters.pw, cornerRadius)) } @@ -83,9 +88,26 @@ export const tssop = ( } const m = Math.min(1, parameters.p / 2) - const sw = - parameters.w - (parameters.legsoutside ? 0 : parameters.pl * 2) - 0.2 - const sh = (parameters.num_pins / 2 - 1) * parameters.p + parameters.pw + m + const silkscreenStrokeWidth = parameters.silkscreen_stroke_width ?? 0.1 + // Inset the body outline from the pad edges by IPC's silk-to-pad clearance + // plus half the stroke. The previous flat `- 0.2` was a total inset of 0.1 + // per side, which is less than the stroke half-width alone once the pads + // reach the body (e.g. `tssop10_w3mm_p0.5mm` overlapped by 0.025mm). + const silkPadClearance = 0.2 + silkscreenStrokeWidth / 2 + // Derive both from the pads that were actually placed, rather than from `w`. + // Fine-pitch parts shift the pads inward (`wForPads`), so a width-based + // formula drifts from the real copper — that drift is what put the outline on + // the pads for `tssop10_w3mm_p0.5mm`. + const sw = Math.min( + parameters.w - (parameters.legsoutside ? 0 : parameters.pl * 2) - 0.2, + (padInnerHalfX - silkPadClearance) * 2, + ) + // The outline's horizontal runs pass above and below the outermost pads, so + // the height needs the same clearance the width gets. + const sh = Math.max( + (parameters.num_pins / 2 - 1) * parameters.p + parameters.pw + m, + (padOuterHalfY + silkPadClearance) * 2, + ) const silkscreenRefText: SilkscreenRef = silkscreenRef( 0, sh / 2 + 0.4, @@ -96,7 +118,7 @@ export const tssop = ( layer: "top", pcb_component_id: "", pcb_silkscreen_path_id: "silkscreen_path_1", - stroke_width: parameters.silkscreen_stroke_width ?? 0.1, + stroke_width: silkscreenStrokeWidth, route: [ { x: -sw / 2, y: -sh / 2 }, { x: -sw / 2, y: sh / 2 }, diff --git a/tests/__snapshots__/sot457.snap.svg b/tests/__snapshots__/sot457.snap.svg index 25cdcc41..9eb2211e 100644 --- a/tests/__snapshots__/sot457.snap.svg +++ b/tests/__snapshots__/sot457.snap.svg @@ -1 +1 @@ -{REF} \ No newline at end of file +{REF} \ No newline at end of file diff --git a/tests/__snapshots__/sot457_wave.snap.svg b/tests/__snapshots__/sot457_wave.snap.svg index da07d7ce..f7831161 100644 --- a/tests/__snapshots__/sot457_wave.snap.svg +++ b/tests/__snapshots__/sot457_wave.snap.svg @@ -1 +1 @@ -{REF} \ No newline at end of file +{REF} \ No newline at end of file diff --git a/tests/__snapshots__/tssop20_w6.5mm_p0.65mm.snap.svg b/tests/__snapshots__/tssop20_w6.5mm_p0.65mm.snap.svg index 569ca803..01f042af 100644 --- a/tests/__snapshots__/tssop20_w6.5mm_p0.65mm.snap.svg +++ b/tests/__snapshots__/tssop20_w6.5mm_p0.65mm.snap.svg @@ -1 +1 @@ -{REF} \ No newline at end of file +{REF} \ No newline at end of file diff --git a/tests/__snapshots__/tssop8.snap.svg b/tests/__snapshots__/tssop8.snap.svg index 7ec8ea1c..7c1e6df9 100644 --- a/tests/__snapshots__/tssop8.snap.svg +++ b/tests/__snapshots__/tssop8.snap.svg @@ -1 +1 @@ -{REF} \ No newline at end of file +{REF} \ No newline at end of file diff --git a/tests/__snapshots__/tssop8_thermalpad2x2.4mm.snap.svg b/tests/__snapshots__/tssop8_thermalpad2x2.4mm.snap.svg index 33308023..bddf18ec 100644 --- a/tests/__snapshots__/tssop8_thermalpad2x2.4mm.snap.svg +++ b/tests/__snapshots__/tssop8_thermalpad2x2.4mm.snap.svg @@ -1 +1 @@ -{REF} \ No newline at end of file +{REF} \ No newline at end of file diff --git a/tests/__snapshots__/tssop8_w5.3mm_p1.27mm.snap.svg b/tests/__snapshots__/tssop8_w5.3mm_p1.27mm.snap.svg index 361e4396..ea27bf99 100644 --- a/tests/__snapshots__/tssop8_w5.3mm_p1.27mm.snap.svg +++ b/tests/__snapshots__/tssop8_w5.3mm_p1.27mm.snap.svg @@ -1 +1 @@ -{REF} \ No newline at end of file +{REF} \ No newline at end of file diff --git a/tests/kicad-parity/__snapshots__/sot457.snap.svg b/tests/kicad-parity/__snapshots__/sot457.snap.svg index 4f6387bc..79f2e780 100644 --- a/tests/kicad-parity/__snapshots__/sot457.snap.svg +++ b/tests/kicad-parity/__snapshots__/sot457.snap.svg @@ -1 +1 @@ -{REF}REF**Diff: 0.00% \ No newline at end of file +{REF}REF**Diff: 0.00% \ No newline at end of file diff --git a/tests/kicad-parity/__snapshots__/tssop10.snap.svg b/tests/kicad-parity/__snapshots__/tssop10.snap.svg index 7f14ac14..8f714b95 100644 --- a/tests/kicad-parity/__snapshots__/tssop10.snap.svg +++ b/tests/kicad-parity/__snapshots__/tssop10.snap.svg @@ -1 +1 @@ -{REF}REF**Diff: 0.00% \ No newline at end of file +{REF}REF**Diff: 0.00% \ No newline at end of file diff --git a/tests/tssop.test.ts b/tests/tssop.test.ts index 73d5facf..65bdf811 100644 --- a/tests/tssop.test.ts +++ b/tests/tssop.test.ts @@ -25,3 +25,71 @@ test("tssop8", () => { const svgContent = convertCircuitJsonToPcbSvg(soup) expect(svgContent).toMatchSvgSnapshot(import.meta.path, "tssop8") }) + +test("tssop and sot457 silkscreen keeps clear of every pad", () => { + // Both outlines used to be derived from `w` rather than from the pads that + // were actually placed. Fine-pitch tssop shifts its pads inward, so the + // outline drifted onto the copper (tssop10_w3mm_p0.5mm overlapped by + // 0.025mm), and sot457's pin 1 marker was offset by a multiple of the pad + // *width*, which lands on the pad when pl and pw are set independently. + const footprints = [ + "tssop10_w3mm_p0.5mm", + "tssop8", + "tssop14", + "tssop16", + "tssop20", + "tssop28", + "tssop20_w6.5mm_p0.65mm", + "sot457", + "sot457_w2_pl1.5_pw0.6", + ] + + const distanceToPad = (point: { x: number; y: number }, pad: any) => { + const dx = Math.max( + pad.x - pad.width / 2 - point.x, + 0, + point.x - (pad.x + pad.width / 2), + ) + const dy = Math.max( + pad.y - pad.height / 2 - point.y, + 0, + point.y - (pad.y + pad.height / 2), + ) + return Math.sqrt(dx * dx + dy * dy) + } + + for (const name of footprints) { + const circuitJson = fp.string(name).circuitJson() + const pads = circuitJson.filter((e) => e.type === "pcb_smtpad") as any[] + const paths = circuitJson.filter( + (e) => e.type === "pcb_silkscreen_path", + ) as any[] + + expect(paths.length).toBeGreaterThan(0) + + let minClearance = Number.POSITIVE_INFINITY + for (const path of paths) { + const strokeHalfWidth = (path.stroke_width ?? 0) / 2 + for (let i = 0; i < path.route.length - 1; i++) { + const a = path.route[i] + const b = path.route[i + 1] + for (let t = 0; t <= 1; t += 0.01) { + const point = { x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t } + for (const pad of pads) { + minClearance = Math.min( + minClearance, + distanceToPad(point, pad) - strokeHalfWidth, + ) + } + } + } + } + + expect(Number.isFinite(minClearance)).toBe(true) + // IPC silkscreen-to-pad clearance, the same 0.2mm KiCad leaves. + expect({ name, clears: minClearance >= 0.2 - 1e-6 }).toEqual({ + name, + clears: true, + }) + } +})