From a6e6b442b8ef5bc74ea35486ef3c3b50f5363bae Mon Sep 17 00:00:00 2001 From: hrithik18k Date: Mon, 5 Oct 2026 15:59:15 +0530 Subject: [PATCH 1/2] fix: add TI D0008A SOIC-8 copper land preset --- README.md | 11 +++ src/fn/index.ts | 1 + src/fn/tid0008a.ts | 29 +++++++ src/footprinter.ts | 1 + .../tid0008a-rightside-norefdes.snap.svg | 1 + tests/__snapshots__/tid0008a.snap.svg | 1 + tests/tid0008a.test.ts | 79 +++++++++++++++++++ 7 files changed, 123 insertions(+) create mode 100644 src/fn/tid0008a.ts create mode 100644 tests/__snapshots__/tid0008a-rightside-norefdes.snap.svg create mode 100644 tests/__snapshots__/tid0008a.snap.svg create mode 100644 tests/tid0008a.test.ts diff --git a/README.md b/README.md index fa6d47f6..4a920d7e 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ Here are some example footprinter strings: cap0402 res0805 soic8_p1.27mm +tid0008a dip16 pinrow10 pinrow6_rows2_cols5_p2.54mm_py5.08mm_missing(3,4,8,9) @@ -33,6 +34,16 @@ do219ad sod323he ``` +For the TI D0008A SOIC-8 package (for example, TLC555CD), use `tid0008a` +or `fp().tid0008a()`. Its fixed copper lands follow the example board layout +in the [TLC555 datasheet](https://www.ti.com/lit/ds/symlink/tlc555.pdf), +drawing 4214825/C: 1.55 × 0.60 mm pads, 1.27 mm pitch, 5.40 mm between +pad-row centers, and 0.05 mm corner radius. It reuses the SOIC silkscreen +and courtyard generator; it does not define solder-mask or paste openings. +Use generic `soic8` with explicit dimensions for other land patterns. +Existing `soic8` defaults are unchanged. Common options such as +`tid0008a_norefdes` and `tid0008a_pin1location(topside,left)` also work. + You can use these like so: ```tsx diff --git a/src/fn/index.ts b/src/fn/index.ts index 37566bbd..2d6255fa 100644 --- a/src/fn/index.ts +++ b/src/fn/index.ts @@ -10,6 +10,7 @@ export { led5050 } from "./led5050" export { res } from "./res" export { bga } from "./bga" export { soic } from "./soic" +export { tid0008a } from "./tid0008a" export { quad } from "./quad" export { qfn } from "./qfn" export { tqfp } from "./tqfp" diff --git a/src/fn/tid0008a.ts b/src/fn/tid0008a.ts new file mode 100644 index 00000000..573695fc --- /dev/null +++ b/src/fn/tid0008a.ts @@ -0,0 +1,29 @@ +import { z } from "zod" +import { base_def } from "../helpers/zod/base_def" +import { soic_def, soicWithoutParsing } from "./soic" + +export const tid0008a_def = base_def.extend({ + fn: z.literal("tid0008a"), + num_pins: z.literal(8).default(8), +}) + +/** TI D0008A, drawing 4214825/C: example board layout (all dimensions in mm). + * https://www.ti.com/lit/ds/symlink/tlc555.pdf + */ +export const tid0008a = (raw_params: z.input) => { + const parameters = soic_def.parse({ + ...tid0008a_def.parse(raw_params), + // SOIC's w is the outer copper span: 5.4 row spacing + 1.55 pad length. + w: 6.95, + p: 1.27, + pl: 1.55, + pw: 0.6, + }) + const circuitJson = soicWithoutParsing(parameters) + for (const element of circuitJson) { + if (element.type === "pcb_smtpad" && element.shape === "rect") { + element.corner_radius = 0.05 + } + } + return { circuitJson, parameters } +} diff --git a/src/footprinter.ts b/src/footprinter.ts index 2c60ae78..8fa70fd5 100644 --- a/src/footprinter.ts +++ b/src/footprinter.ts @@ -385,6 +385,7 @@ export type Footprinter = { micromelf: () => FootprinterParamsBuilder<"w" | "h" | "p" | "pw" | "pl"> ms013: () => FootprinterParamsBuilder<"w" | "p"> ms012: () => FootprinterParamsBuilder<"w" | "p"> + tid0008a: () => FootprinterParamsBuilder lqfp: ( num_pins?: number, ) => FootprinterParamsBuilder<"w" | "h" | "pl" | "pw" | "pillpads"> diff --git a/tests/__snapshots__/tid0008a-rightside-norefdes.snap.svg b/tests/__snapshots__/tid0008a-rightside-norefdes.snap.svg new file mode 100644 index 00000000..3bcb67ea --- /dev/null +++ b/tests/__snapshots__/tid0008a-rightside-norefdes.snap.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tests/__snapshots__/tid0008a.snap.svg b/tests/__snapshots__/tid0008a.snap.svg new file mode 100644 index 00000000..cab54997 --- /dev/null +++ b/tests/__snapshots__/tid0008a.snap.svg @@ -0,0 +1 @@ +{REF} \ No newline at end of file diff --git a/tests/tid0008a.test.ts b/tests/tid0008a.test.ts new file mode 100644 index 00000000..31de99e7 --- /dev/null +++ b/tests/tid0008a.test.ts @@ -0,0 +1,79 @@ +import { expect, test } from "bun:test" +import { convertCircuitJsonToPcbSvg } from "circuit-to-svg" +import { fp, getFootprintNames } from "../src/footprinter" +import { tid0008a_def } from "../src/fn/tid0008a" + +test("tid0008a matches TI drawing 4214825/C example copper lands", () => { + const circuit = fp.string("tid0008a").circuitJson() + const pads = circuit.filter((el) => el.type === "pcb_smtpad") + // Independent top-view coordinates from TI's 5.4 mm row spacing and + // 1.27 mm pitch, with counterclockwise numbering starting at top left. + const expected = [ + [-2.7, 1.905], + [-2.7, 0.635], + [-2.7, -0.635], + [-2.7, -1.905], + [2.7, -1.905], + [2.7, -0.635], + [2.7, 0.635], + [2.7, 1.905], + ] + expect(pads).toHaveLength(8) + pads.forEach((pad, i) => { + expect(pad.shape).toBe("rect") + if (pad.shape !== "rect") throw new Error("Expected rectangular pad") + expect(pad.port_hints).toEqual([String(i + 1)]) + expect(pad.layer).toBe("top") + expect(pad.x).toBeCloseTo(expected[i]![0]!, 6) + expect(pad.y).toBeCloseTo(expected[i]![1]!, 6) + expect(pad.width).toBeCloseTo(1.55, 6) + expect(pad.height).toBeCloseTo(0.6, 6) + expect(pad.corner_radius).toBeCloseTo(0.05, 6) + }) + expect(convertCircuitJsonToPcbSvg(circuit)).toMatchSvgSnapshot( + import.meta.path, + "tid0008a", + ) +}) + +test("tid0008a is discoverable and supports string and typed builder APIs", () => { + expect(getFootprintNames()).toContain("tid0008a") + expect(fp().tid0008a().circuitJson()).toEqual( + fp.string("tid0008a").circuitJson(), + ) + expect(fp.string("tid0008a").json()).toMatchObject({ + fn: "tid0008a", + num_pins: 8, + w: 6.95, + p: 1.27, + pl: 1.55, + pw: 0.6, + }) + expect(tid0008a_def.safeParse({ fn: "tid0008a", num_pins: 16 }).success).toBe( + false, + ) +}) + +test("tid0008a supports common footprint options", () => { + const circuit = fp() + .tid0008a() + .norefdes() + .pin1location("rightside", "bottom") + .circuitJson() + expect(circuit).toEqual( + fp.string("tid0008a_norefdes_pin1location(rightside,bottom)").circuitJson(), + ) + expect( + circuit + .filter((el) => el.type === "pcb_silkscreen_text") + .every((el) => el.text === ""), + ).toBe(true) + const pin1 = circuit.find( + (el) => el.type === "pcb_smtpad" && el.port_hints?.includes("1"), + ) + expect(pin1).toMatchObject({ x: 2.7, y: -1.905 }) + expect(convertCircuitJsonToPcbSvg(circuit)).toMatchSvgSnapshot( + import.meta.path, + "tid0008a-rightside-norefdes", + ) +}) From 6c594a5c446be9521085a247d2ff69c8dc0853ab Mon Sep 17 00:00:00 2001 From: hrithik18k Date: Mon, 5 Oct 2026 16:28:53 +0530 Subject: [PATCH 2/2] refactor: select TI D0008A through the SOIC package option --- README.md | 10 ++-- src/fn/index.ts | 1 - src/fn/soic.ts | 39 ++++++++++++++- src/fn/tid0008a.ts | 29 ----------- src/footprinter.ts | 2 +- ...ic8-ti-d0008a-rightside-norefdes.snap.svg} | 0 ...008a.snap.svg => soic8-ti-d0008a.snap.svg} | 0 ...id0008a.test.ts => soic-ti-d0008a.test.ts} | 50 ++++++++++++------- 8 files changed, 77 insertions(+), 54 deletions(-) delete mode 100644 src/fn/tid0008a.ts rename tests/__snapshots__/{tid0008a-rightside-norefdes.snap.svg => soic8-ti-d0008a-rightside-norefdes.snap.svg} (100%) rename tests/__snapshots__/{tid0008a.snap.svg => soic8-ti-d0008a.snap.svg} (100%) rename tests/{tid0008a.test.ts => soic-ti-d0008a.test.ts} (56%) diff --git a/README.md b/README.md index 4a920d7e..08ab8340 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Here are some example footprinter strings: cap0402 res0805 soic8_p1.27mm -tid0008a +soic8_package(D0008A) dip16 pinrow10 pinrow6_rows2_cols5_p2.54mm_py5.08mm_missing(3,4,8,9) @@ -34,15 +34,17 @@ do219ad sod323he ``` -For the TI D0008A SOIC-8 package (for example, TLC555CD), use `tid0008a` -or `fp().tid0008a()`. Its fixed copper lands follow the example board layout +For the TI D0008A SOIC-8 package (for example, TLC555CD), use `soic8_package(D0008A)` +or `fp().soic(8).package("D0008A")`. Its default copper lands follow the example board layout in the [TLC555 datasheet](https://www.ti.com/lit/ds/symlink/tlc555.pdf), drawing 4214825/C: 1.55 × 0.60 mm pads, 1.27 mm pitch, 5.40 mm between pad-row centers, and 0.05 mm corner radius. It reuses the SOIC silkscreen and courtyard generator; it does not define solder-mask or paste openings. +Explicit dimensions override package defaults; for example, +`soic8_package(D0008A)_pl1.6mm`. The package option requires eight pins. Use generic `soic8` with explicit dimensions for other land patterns. Existing `soic8` defaults are unchanged. Common options such as -`tid0008a_norefdes` and `tid0008a_pin1location(topside,left)` also work. +`soic8_package(D0008A)_norefdes` and `soic8_package(D0008A)_pin1location(topside,left)` also work. You can use these like so: diff --git a/src/fn/index.ts b/src/fn/index.ts index 2d6255fa..37566bbd 100644 --- a/src/fn/index.ts +++ b/src/fn/index.ts @@ -10,7 +10,6 @@ export { led5050 } from "./led5050" export { res } from "./res" export { bga } from "./bga" export { soic } from "./soic" -export { tid0008a } from "./tid0008a" export { quad } from "./quad" export { qfn } from "./qfn" export { tqfp } from "./tqfp" diff --git a/src/fn/soic.ts b/src/fn/soic.ts index fa3488e0..e778dbaf 100644 --- a/src/fn/soic.ts +++ b/src/fn/soic.ts @@ -64,7 +64,38 @@ export const extendSoicDef = (newDefaults: { > }) -export const soic_def = extendSoicDef({}) +const soicPackage = z.preprocess( + (value) => + typeof value === "string" + ? value.replace(/^\((.*)\)$/, "$1").toUpperCase() + : value, + z.literal("D0008A").optional(), +) + +export const soic_def = z.preprocess( + (raw) => { + if (!raw || typeof raw !== "object") return raw + const input = raw as Record + const selectedPackage = soicPackage.safeParse(input.package) + if (!selectedPackage.success || selectedPackage.data !== "D0008A") + return raw + // TI drawing 4214825/C, example board layout: + // https://www.ti.com/lit/ds/symlink/tlc555.pdf + // w is the outer copper span: 5.4 mm row spacing + 1.55 mm pad length. + return { w: 6.95, p: 1.27, pl: 1.55, pw: 0.6, ...input } + }, + extendSoicDef({}) + .and(z.object({ package: soicPackage })) + .superRefine((value, ctx) => { + if (value.package === "D0008A" && value.num_pins !== 8) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ["num_pins"], + message: "The TI D0008A package requires 8 pins", + }) + } + }), +) export type SoicInput = z.infer export const getCcwSoicCoords = (parameters: { @@ -111,6 +142,7 @@ export const soic = (raw_params: { num_pins: number w: number p?: number + package?: string id?: string | number od?: string | number }): { circuitJson: AnyCircuitElement[]; parameters: SoicInput } => { @@ -123,7 +155,10 @@ export const soic = (raw_params: { export const soicWithoutParsing = (parameters: z.infer) => { const pads: AnyCircuitElement[] = [] - const cornerRadius = Math.min(parameters.pl, parameters.pw) / 8 + const cornerRadius = + parameters.package === "D0008A" + ? Math.min(0.05, parameters.pl / 2, parameters.pw / 2) + : Math.min(parameters.pl, parameters.pw) / 8 let maxPadExtentX = 0 let maxPadExtentY = 0 for (let i = 0; i < parameters.num_pins; i++) { diff --git a/src/fn/tid0008a.ts b/src/fn/tid0008a.ts deleted file mode 100644 index 573695fc..00000000 --- a/src/fn/tid0008a.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { z } from "zod" -import { base_def } from "../helpers/zod/base_def" -import { soic_def, soicWithoutParsing } from "./soic" - -export const tid0008a_def = base_def.extend({ - fn: z.literal("tid0008a"), - num_pins: z.literal(8).default(8), -}) - -/** TI D0008A, drawing 4214825/C: example board layout (all dimensions in mm). - * https://www.ti.com/lit/ds/symlink/tlc555.pdf - */ -export const tid0008a = (raw_params: z.input) => { - const parameters = soic_def.parse({ - ...tid0008a_def.parse(raw_params), - // SOIC's w is the outer copper span: 5.4 row spacing + 1.55 pad length. - w: 6.95, - p: 1.27, - pl: 1.55, - pw: 0.6, - }) - const circuitJson = soicWithoutParsing(parameters) - for (const element of circuitJson) { - if (element.type === "pcb_smtpad" && element.shape === "rect") { - element.corner_radius = 0.05 - } - } - return { circuitJson, parameters } -} diff --git a/src/footprinter.ts b/src/footprinter.ts index 8fa70fd5..5daa3f2e 100644 --- a/src/footprinter.ts +++ b/src/footprinter.ts @@ -167,6 +167,7 @@ export type Footprinter = { soic: ( num_pins?: number, ) => FootprinterParamsBuilder< + | "package" | "w" | "p" | "pw" @@ -385,7 +386,6 @@ export type Footprinter = { micromelf: () => FootprinterParamsBuilder<"w" | "h" | "p" | "pw" | "pl"> ms013: () => FootprinterParamsBuilder<"w" | "p"> ms012: () => FootprinterParamsBuilder<"w" | "p"> - tid0008a: () => FootprinterParamsBuilder lqfp: ( num_pins?: number, ) => FootprinterParamsBuilder<"w" | "h" | "pl" | "pw" | "pillpads"> diff --git a/tests/__snapshots__/tid0008a-rightside-norefdes.snap.svg b/tests/__snapshots__/soic8-ti-d0008a-rightside-norefdes.snap.svg similarity index 100% rename from tests/__snapshots__/tid0008a-rightside-norefdes.snap.svg rename to tests/__snapshots__/soic8-ti-d0008a-rightside-norefdes.snap.svg diff --git a/tests/__snapshots__/tid0008a.snap.svg b/tests/__snapshots__/soic8-ti-d0008a.snap.svg similarity index 100% rename from tests/__snapshots__/tid0008a.snap.svg rename to tests/__snapshots__/soic8-ti-d0008a.snap.svg diff --git a/tests/tid0008a.test.ts b/tests/soic-ti-d0008a.test.ts similarity index 56% rename from tests/tid0008a.test.ts rename to tests/soic-ti-d0008a.test.ts index 31de99e7..3f0dc1f3 100644 --- a/tests/tid0008a.test.ts +++ b/tests/soic-ti-d0008a.test.ts @@ -1,10 +1,9 @@ import { expect, test } from "bun:test" import { convertCircuitJsonToPcbSvg } from "circuit-to-svg" -import { fp, getFootprintNames } from "../src/footprinter" -import { tid0008a_def } from "../src/fn/tid0008a" +import { fp } from "../src/footprinter" -test("tid0008a matches TI drawing 4214825/C example copper lands", () => { - const circuit = fp.string("tid0008a").circuitJson() +test("soic8_package(D0008A) matches TI drawing 4214825/C example copper lands", () => { + const circuit = fp.string("soic8_package(D0008A)").circuitJson() const pads = circuit.filter((el) => el.type === "pcb_smtpad") // Independent top-view coordinates from TI's 5.4 mm row spacing and // 1.27 mm pitch, with counterclockwise numbering starting at top left. @@ -32,36 +31,53 @@ test("tid0008a matches TI drawing 4214825/C example copper lands", () => { }) expect(convertCircuitJsonToPcbSvg(circuit)).toMatchSvgSnapshot( import.meta.path, - "tid0008a", + "soic8-ti-d0008a", ) }) -test("tid0008a is discoverable and supports string and typed builder APIs", () => { - expect(getFootprintNames()).toContain("tid0008a") - expect(fp().tid0008a().circuitJson()).toEqual( - fp.string("tid0008a").circuitJson(), +test("soic8_package(D0008A) supports string and typed builder APIs", () => { + expect(fp().soic(8).package("D0008A").circuitJson()).toEqual( + fp.string("soic8_package(D0008A)").circuitJson(), ) - expect(fp.string("tid0008a").json()).toMatchObject({ - fn: "tid0008a", + expect(fp.string("soic8_package(D0008A)").json()).toMatchObject({ + fn: "soic", + package: "D0008A", num_pins: 8, w: 6.95, p: 1.27, pl: 1.55, pw: 0.6, }) - expect(tid0008a_def.safeParse({ fn: "tid0008a", num_pins: 16 }).success).toBe( - false, + expect(() => fp.string("soic16_package(D0008A)").circuitJson()).toThrow( + "requires 8 pins", ) + expect(() => fp.string("soic8_package(UNKNOWN)").circuitJson()).toThrow() + expect(fp.string("soic8_package(d0008a)").json()).toEqual( + fp.string("soic8_package(D0008A)").json(), + ) + for (const definition of [ + "soic8_package(D0008A)_pl1.6mm", + "soic8_pl1.6mm_package(D0008A)", + ]) { + expect(fp.string(definition).json()).toMatchObject({ + package: "D0008A", + pl: 1.6, + w: 6.95, + }) + } }) -test("tid0008a supports common footprint options", () => { +test("soic8_package(D0008A) supports common footprint options", () => { const circuit = fp() - .tid0008a() + .soic(8) + .package("D0008A") .norefdes() .pin1location("rightside", "bottom") .circuitJson() expect(circuit).toEqual( - fp.string("tid0008a_norefdes_pin1location(rightside,bottom)").circuitJson(), + fp + .string("soic8_package(D0008A)_norefdes_pin1location(rightside,bottom)") + .circuitJson(), ) expect( circuit @@ -74,6 +90,6 @@ test("tid0008a supports common footprint options", () => { expect(pin1).toMatchObject({ x: 2.7, y: -1.905 }) expect(convertCircuitJsonToPcbSvg(circuit)).toMatchSvgSnapshot( import.meta.path, - "tid0008a-rightside-norefdes", + "soic8-ti-d0008a-rightside-norefdes", ) })