diff --git a/src/fn/index.ts b/src/fn/index.ts index c3ee18a1..9f1f3ebf 100644 --- a/src/fn/index.ts +++ b/src/fn/index.ts @@ -80,3 +80,5 @@ export { sot343 } from "./sot343" export { m2host } from "./m2host" export { mountedpcbmodule } from "./mountedpcbmodule" export { to92l } from "./to92l" +export { pdip } from "./pdip" +export { spdip } from "./pdip" diff --git a/src/fn/pdip.ts b/src/fn/pdip.ts new file mode 100644 index 00000000..78619d6b --- /dev/null +++ b/src/fn/pdip.ts @@ -0,0 +1,107 @@ +import type { AnyCircuitElement } from "circuit-json" +import { z } from "zod" +import { base_def } from "../helpers/zod/base_def" +import { dip } from "./dip" + +/** + * PDIP (Plastic Dual In-line Package) footprint. + * + * PDIP is the standard plastic version of DIP with: + * - 300mil (7.62mm) row spacing (default) + * - 100mil (2.54mm) pin pitch + * + * Supported variants: pdip8, pdip14, pdip16, pdip18, pdip20, pdip24, pdip28, pdip40 + * + * Examples: "pdip8", "pdip16", "pdip8_w7.62mm" + */ +export const pdip_def = base_def.extend({ + fn: z.string(), + num_pins: z.number().default(8), + w: z.string().optional(), + p: z.string().default("2.54mm"), + id: z.string().optional(), + od: z.string().optional(), + string: z.string().optional(), +}) + +export const pdip = ( + raw_params: z.input, +): { circuitJson: AnyCircuitElement[]; parameters: any } => { + const parameters = pdip_def.parse(raw_params) + + // PDIP uses identical geometry to DIP. + // Default width: 300mil (7.62mm) for ≤28 pins, 600mil (15.24mm) for 40-pin + const wide = parameters.num_pins > 28 + const rowSpacing = parameters.w + ? parseFloat(parameters.w) + : wide + ? 15.24 + : 7.62 + const pitch = parameters.p ? parseFloat(parameters.p) : 2.54 + + const dipResult = dip({ + dip: true, + fn: "dip", + num_pins: parameters.num_pins, + w: rowSpacing, + p: pitch, + ...(parameters.id ? { id: parameters.id } : {}), + ...(parameters.od ? { od: parameters.od } : {}), + } as any) + + return { + circuitJson: dipResult.circuitJson, + parameters: { + ...dipResult.parameters, + fn: parameters.fn, + }, + } +} + +/** + * SPDIP (Shrink Plastic Dual In-line Package) footprint. + * + * SPDIP uses a narrower 70mil (1.778mm) pin pitch compared to standard DIP's + * 100mil (2.54mm) pitch. Row spacing is 600mil (15.24mm). + * + * Common use case: SPDIP-28 (e.g., PIC18F2X2X microcontrollers) + * + * Examples: "spdip28" + */ +export const spdip_def = base_def.extend({ + fn: z.string(), + num_pins: z.number().default(28), + w: z.string().optional(), + p: z.string().default("1.778mm"), + id: z.string().optional(), + od: z.string().optional(), + string: z.string().optional(), +}) + +export const spdip = ( + raw_params: z.input, +): { circuitJson: AnyCircuitElement[]; parameters: any } => { + const parameters = spdip_def.parse(raw_params) + + // SPDIP: 600mil (15.24mm) row spacing, 70mil (1.778mm) pitch + const rowSpacing = parameters.w ? parseFloat(parameters.w) : 15.24 + const pitch = parameters.p ? parseFloat(parameters.p) : 1.778 + + const dipResult = dip({ + dip: true, + fn: "dip", + num_pins: parameters.num_pins, + w: rowSpacing, + p: pitch, + id: parameters.id ?? "0.8mm", + od: parameters.od ?? "1.6mm", + } as any) + + return { + circuitJson: dipResult.circuitJson, + parameters: { + ...dipResult.parameters, + fn: parameters.fn, + }, + } +} diff --git a/tests/__snapshots__/pdip14.snap.svg b/tests/__snapshots__/pdip14.snap.svg new file mode 100644 index 00000000..dc26ac0b --- /dev/null +++ b/tests/__snapshots__/pdip14.snap.svg @@ -0,0 +1 @@ +{REF}{pin1}{pin2}{pin3}{pin4}{pin5}{pin6}{pin7}{pin8}{pin9}{pin10}{pin11}{pin12}{pin13}{pin14} \ No newline at end of file diff --git a/tests/__snapshots__/pdip16.snap.svg b/tests/__snapshots__/pdip16.snap.svg new file mode 100644 index 00000000..595c0386 --- /dev/null +++ b/tests/__snapshots__/pdip16.snap.svg @@ -0,0 +1 @@ +{REF}{pin1}{pin2}{pin3}{pin4}{pin5}{pin6}{pin7}{pin8}{pin9}{pin10}{pin11}{pin12}{pin13}{pin14}{pin15}{pin16} \ No newline at end of file diff --git a/tests/__snapshots__/pdip8.snap.svg b/tests/__snapshots__/pdip8.snap.svg new file mode 100644 index 00000000..4a51d472 --- /dev/null +++ b/tests/__snapshots__/pdip8.snap.svg @@ -0,0 +1 @@ +{REF}{pin1}{pin2}{pin3}{pin4}{pin5}{pin6}{pin7}{pin8} \ No newline at end of file diff --git a/tests/__snapshots__/spdip28.snap.svg b/tests/__snapshots__/spdip28.snap.svg new file mode 100644 index 00000000..93e17aa7 --- /dev/null +++ b/tests/__snapshots__/spdip28.snap.svg @@ -0,0 +1 @@ +{REF}{pin1}{pin2}{pin3}{pin4}{pin5}{pin6}{pin7}{pin8}{pin9}{pin10}{pin11}{pin12}{pin13}{pin14}{pin15}{pin16}{pin17}{pin18}{pin19}{pin20}{pin21}{pin22}{pin23}{pin24}{pin25}{pin26}{pin27}{pin28} \ No newline at end of file diff --git a/tests/pdip.test.ts b/tests/pdip.test.ts new file mode 100644 index 00000000..63a477e7 --- /dev/null +++ b/tests/pdip.test.ts @@ -0,0 +1,45 @@ +import { test, expect } from "bun:test" +import { convertCircuitJsonToPcbSvg } from "circuit-to-svg" +import { fp } from "../src/footprinter" +import type { AnyCircuitElement } from "circuit-json" + +test("pdip8", () => { + const circuitJson = fp.string("pdip8").circuitJson() as AnyCircuitElement[] + const svgContent = convertCircuitJsonToPcbSvg(circuitJson) + expect(svgContent).toMatchSvgSnapshot(import.meta.path, "pdip8") +}) + +test("pdip14", () => { + const circuitJson = fp.string("pdip14").circuitJson() as AnyCircuitElement[] + const svgContent = convertCircuitJsonToPcbSvg(circuitJson) + expect(svgContent).toMatchSvgSnapshot(import.meta.path, "pdip14") +}) + +test("pdip16", () => { + const circuitJson = fp.string("pdip16").circuitJson() as AnyCircuitElement[] + const svgContent = convertCircuitJsonToPcbSvg(circuitJson) + expect(svgContent).toMatchSvgSnapshot(import.meta.path, "pdip16") +}) + +test("pdip8 parameters", () => { + const json = fp.string("pdip8").json() + expect(json.num_pins).toBe(8) + expect(json.p).toBe(2.54) + expect(json.w).toBe(7.62) +}) + +// SPDIP tests +test("spdip28", () => { + const circuitJson = fp.string("spdip28").circuitJson() as AnyCircuitElement[] + const svgContent = convertCircuitJsonToPcbSvg(circuitJson) + expect(svgContent).toMatchSvgSnapshot(import.meta.path, "spdip28") +}) + +test("spdip28 parameters", () => { + const json = fp.string("spdip28").json() + expect(json.num_pins).toBe(28) + // 70mil pitch + expect(json.p).toBeCloseTo(1.778, 2) + // 600mil row spacing + expect(json.w).toBeCloseTo(15.24, 1) +})