From d56c8aacf753836c2a496acfa544a2e5b340a861 Mon Sep 17 00:00:00 2001 From: yanyishuai <1093994647@qq.com> Date: Tue, 22 Sep 2026 10:46:39 +0800 Subject: [PATCH] fix(quad): reject zero pitch instead of emitting NaN pads (#871) --- src/fn/quad.ts | 21 +++++++++++++++++++-- tests/quad-zero-pitch.test.ts | 17 +++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 tests/quad-zero-pitch.test.ts diff --git a/src/fn/quad.ts b/src/fn/quad.ts index 69742ffe..b16b095a 100644 --- a/src/fn/quad.ts +++ b/src/fn/quad.ts @@ -107,6 +107,21 @@ export const quadTransform = >( const horizontal_pitch = v.px ?? v.p const vertical_pitch = v.py ?? v.p + // Explicit zero/negative pitch must not silently produce NaN pad coords + // (e.g. `lcc_p0mm` / `qfn16_p0mm`). Treat non-positive pitch as invalid. + for (const [name, pitch] of [ + ["p", v.p], + ["px", v.px], + ["py", v.py], + ] as const) { + if (pitch === undefined || pitch === null) continue + if (!(pitch > 0) || !Number.isFinite(pitch)) { + throw new Error( + `Invalid ${name}=${pitch}: pitch must be a positive finite number`, + ) + } + } + if (!v.p && !v.pw && !v.pl && v.w) { // HACK: This is wayyy underspecified const approx_pin_size_of_side = horizontal_side_pin_count + 4 @@ -122,13 +137,15 @@ export const quadTransform = >( v.p = (horizontalPitch + verticalPitch) / 2 } - if (!v.w && !v.h && v.p) { + // Use `> 0` (not truthiness) so zero pitch cannot skip body sizing and + // later yield NaN coordinates in getQuadCoords. + if (!v.w && !v.h && v.p != null && v.p > 0) { // HACK: underspecified v.w = horizontal_pitch * (horizontal_side_pin_count + 4) v.h = vertical_pitch * (vertical_side_pin_count + 4) } - if (v.p && !v.pw && !v.pl) { + if (v.p != null && v.p > 0 && !v.pw && !v.pl) { v.pw = v.p / 2 v.pl = v.p / 2 } else if (!v.pw) { diff --git a/tests/quad-zero-pitch.test.ts b/tests/quad-zero-pitch.test.ts new file mode 100644 index 00000000..32c3c6f7 --- /dev/null +++ b/tests/quad-zero-pitch.test.ts @@ -0,0 +1,17 @@ +import { expect, test } from "bun:test" +import { fp } from "../src/footprinter" + +test("quad-family rejects explicit zero pitch instead of emitting NaN pads", () => { + expect(() => fp.string("lcc_p0mm").circuitJson()).toThrow(/pitch/i) + expect(() => fp.string("qfn16_p0mm").circuitJson()).toThrow(/pitch/i) +}) + +test("valid lcc pitch still produces finite pad coordinates", () => { + const soup = fp.string("lcc68_w24.2_h24.2_p1.27mm").circuitJson() + const pads = soup.filter((element) => element.type === "pcb_smtpad") + expect(pads.length).toBeGreaterThan(0) + for (const pad of pads) { + expect(Number.isFinite(pad.x)).toBe(true) + expect(Number.isFinite(pad.y)).toBe(true) + } +})