From b3d024747d050ebadaa5f8226fbc3d577e446d45 Mon Sep 17 00:00:00 2001 From: Claw Explorer Date: Wed, 1 Apr 2026 15:19:05 -0400 Subject: [PATCH] fix: extend BGA row labels beyond Z with double letters (AA, AB, ...) Large BGA packages (>26 rows) produced 'undefined' in port_hints because ALPHABET only has 26 characters. Added bgaRowLabel() helper that uses double letters (AA, AB, ..., AZ, BA, ...) for rows beyond Z. Backwards compatible: rows 0-25 are unchanged (A-Z). Fixes the TODO at src/fn/bga.ts:156. --- src/fn/bga.ts | 5 +-- src/helpers/zod/ALPHABET.ts | 22 ++++++++++ .../bga_30x30_large_grid.snap.svg | 1 + tests/bga-large-grid-row-labels.test.ts | 40 +++++++++++++++++++ tests/bga-row-label-helper.test.ts | 8 ++++ tests/bga-row-label-helper2.test.ts | 11 +++++ 6 files changed, 84 insertions(+), 3 deletions(-) create mode 100644 tests/__snapshots__/bga_30x30_large_grid.snap.svg create mode 100644 tests/bga-large-grid-row-labels.test.ts create mode 100644 tests/bga-row-label-helper.test.ts create mode 100644 tests/bga-row-label-helper2.test.ts diff --git a/src/fn/bga.ts b/src/fn/bga.ts index 8a712184..9667296d 100644 --- a/src/fn/bga.ts +++ b/src/fn/bga.ts @@ -5,7 +5,7 @@ import type { } from "circuit-json" import { rectpad } from "../helpers/rectpad" import { circlepad } from "../helpers/circlepad" -import { ALPHABET } from "../helpers/zod/ALPHABET" +import { ALPHABET, bgaRowLabel } from "../helpers/zod/ALPHABET" import { z } from "zod" import { base_def } from "../helpers/zod/base_def" import { length, distance } from "circuit-json" @@ -153,8 +153,7 @@ export const bga = ( } pin_num -= missing_pins_passed - // TODO handle >26 rows - const portHints = [pin_num, `${ALPHABET[pin_y]}${pin_x + 1}`] + const portHints = [pin_num, `${bgaRowLabel(pin_y)}${pin_x + 1}`] pads.push( parameters.circularpads ? circlepad(portHints, { diff --git a/src/helpers/zod/ALPHABET.ts b/src/helpers/zod/ALPHABET.ts index 768759ae..fff75827 100644 --- a/src/helpers/zod/ALPHABET.ts +++ b/src/helpers/zod/ALPHABET.ts @@ -1 +1,23 @@ export const ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + +/** + * Return a row label for a zero-based row index, extending beyond Z + * with double letters (AA, AB, …, AZ, BA, …). + * + * Rows 0–25 → A … Z (single letter, same as ALPHABET) + * Rows 26–51 → AA … AZ + * Rows 52–77 → BA … BZ + * … and so on. + * + * This keeps backwards compatibility with the existing A–Z labeling + * while supporting large BGA packages (>26 rows). + */ +export function bgaRowLabel(row: number): string { + if (row < 26) { + return ALPHABET[row]! + } + + const groupIndex = Math.floor((row - 26) / 26) + const letterIndex = (row - 26) % 26 + return `${ALPHABET[groupIndex]}${ALPHABET[letterIndex]}` +} diff --git a/tests/__snapshots__/bga_30x30_large_grid.snap.svg b/tests/__snapshots__/bga_30x30_large_grid.snap.svg new file mode 100644 index 00000000..697ebe16 --- /dev/null +++ b/tests/__snapshots__/bga_30x30_large_grid.snap.svg @@ -0,0 +1 @@ +{REF} \ No newline at end of file diff --git a/tests/bga-large-grid-row-labels.test.ts b/tests/bga-large-grid-row-labels.test.ts new file mode 100644 index 00000000..05deab8f --- /dev/null +++ b/tests/bga-large-grid-row-labels.test.ts @@ -0,0 +1,40 @@ +import { test, expect } from "bun:test" +import type { PcbSmtPad } from "circuit-json" +import { convertCircuitJsonToPcbSvg } from "circuit-to-svg" +import { fp } from "../src/footprinter" + +test("bga 30x30 grid has valid row labels beyond Z", () => { + const soup = fp.string("bga900_grid30x30_p0.5").circuitJson() + const pads = soup.filter((el): el is PcbSmtPad => el.type === "pcb_smtpad") + + expect(pads).toHaveLength(900) + + // No pad should have "undefined" in its label + for (const pad of pads) { + expect(pad.port_hints?.[1]).not.toContain("undefined") + } + + // Row 0 = A, Row 25 = Z (single letters) + const pinA1 = pads.find((p) => p.port_hints?.[0] === "1") + expect(pinA1?.port_hints?.[1]).toBe("A1") + + const pinZ1 = pads.find((p) => p.port_hints?.[1] === "Z1") + expect(pinZ1).toBeDefined() + + // Row 26 = AA, Row 27 = AB (double letters) + const pinAA1 = pads.find((p) => p.port_hints?.[1] === "AA1") + expect(pinAA1).toBeDefined() + + const pinAB1 = pads.find((p) => p.port_hints?.[1] === "AB1") + expect(pinAB1).toBeDefined() + + // Row 29 = AD (last row of a 30x30 grid) + const pinAD1 = pads.find((p) => p.port_hints?.[1] === "AD1") + expect(pinAD1).toBeDefined() + + const svgContent = convertCircuitJsonToPcbSvg(soup) + expect(svgContent).toMatchSvgSnapshot( + import.meta.path, + "bga_30x30_large_grid", + ) +}) diff --git a/tests/bga-row-label-helper.test.ts b/tests/bga-row-label-helper.test.ts new file mode 100644 index 00000000..79bbc006 --- /dev/null +++ b/tests/bga-row-label-helper.test.ts @@ -0,0 +1,8 @@ +import { test, expect } from "bun:test" +import { bgaRowLabel, ALPHABET } from "../src/helpers/zod/ALPHABET" + +test("bgaRowLabel matches ALPHABET for rows 0-25", () => { + for (let i = 0; i < 26; i++) { + expect(bgaRowLabel(i)).toBe(ALPHABET[i]) + } +}) diff --git a/tests/bga-row-label-helper2.test.ts b/tests/bga-row-label-helper2.test.ts new file mode 100644 index 00000000..a3a6d295 --- /dev/null +++ b/tests/bga-row-label-helper2.test.ts @@ -0,0 +1,11 @@ +import { test, expect } from "bun:test" +import { bgaRowLabel } from "../src/helpers/zod/ALPHABET" + +test("bgaRowLabel produces double letters after Z", () => { + expect(bgaRowLabel(26)).toBe("AA") + expect(bgaRowLabel(27)).toBe("AB") + expect(bgaRowLabel(51)).toBe("AZ") + expect(bgaRowLabel(52)).toBe("BA") + expect(bgaRowLabel(77)).toBe("BZ") + expect(bgaRowLabel(78)).toBe("CA") +})