diff --git a/src/fn/index.ts b/src/fn/index.ts index 94c0370e..75131465 100644 --- a/src/fn/index.ts +++ b/src/fn/index.ts @@ -81,3 +81,4 @@ export { sot343 } from "./sot343" export { m2host } from "./m2host" export { mountedpcbmodule } from "./mountedpcbmodule" export { to92l } from "./to92l" +export { utdfn } from "./utdfn" diff --git a/src/fn/utdfn.ts b/src/fn/utdfn.ts new file mode 100644 index 00000000..570863a5 --- /dev/null +++ b/src/fn/utdfn.ts @@ -0,0 +1,107 @@ +import type { + AnyCircuitElement, + PcbCourtyardRect, + PcbSilkscreenPath, +} from "circuit-json" +import { z } from "zod" +import { rectpad } from "src/helpers/rectpad" +import { base_def } from "../helpers/zod/base_def" +import { type SilkscreenRef, silkscreenRef } from "src/helpers/silkscreenRef" + +export const utdfn_def = base_def.extend({ + fn: z.string(), + num_pins: z.number().default(4), + w: z.string().default("0.90mm"), + p: z.string().default("0.65mm"), + pl: z.string().default("0.40mm"), + pw: z.string().default("0.25mm"), + epw: z.string().default("0.48mm"), + eph: z.string().default("0.48mm"), + string: z.string().optional(), +}) + +export const utdfn = ( + raw_params: z.input, +): { circuitJson: AnyCircuitElement[]; parameters: any } => { + const parameters = utdfn_def.parse(raw_params) + const w = parseFloat(parameters.w) + const p = parseFloat(parameters.p) + const pl = parseFloat(parameters.pl) + const pw = parseFloat(parameters.pw) + const epw = parseFloat(parameters.epw) + const eph = parseFloat(parameters.eph) + + const pads: AnyCircuitElement[] = [] + + // Generate the 4 pins in CCW order starting from bottom-left (Pin 1) + pads.push(rectpad(1, -w / 2, -p / 2, pl, pw)) + pads.push(rectpad(2, -w / 2, p / 2, pl, pw)) + pads.push(rectpad(3, w / 2, p / 2, pl, pw)) + pads.push(rectpad(4, w / 2, -p / 2, pl, pw)) + + // Pin 5: Center Exposed Pad (EP) + pads.push(rectpad(5, 0, 0, epw, eph)) + + // Silkscreen: 2 horizontal lines + const sw = w + pl + const sh = p + pw + 0.3 + const silkscreenPaths: PcbSilkscreenPath[] = [] + + silkscreenPaths.push({ + layer: "top", + pcb_component_id: "", + pcb_silkscreen_path_id: "", + type: "pcb_silkscreen_path", + route: [ + { x: -sw / 2 + 0.1, y: sh / 2 }, + { x: sw / 2 - 0.1, y: sh / 2 }, + ], + stroke_width: 0.08, + }) + silkscreenPaths.push({ + layer: "top", + pcb_component_id: "", + pcb_silkscreen_path_id: "", + type: "pcb_silkscreen_path", + route: [ + { x: -sw / 2 + 0.1, y: -sh / 2 }, + { x: sw / 2 - 0.1, y: -sh / 2 }, + ], + stroke_width: 0.08, + }) + + // Pin 1 indicator line + silkscreenPaths.push({ + layer: "top", + pcb_component_id: "", + pcb_silkscreen_path_id: "", + type: "pcb_silkscreen_path", + route: [ + { x: -sw / 2 - 0.1, y: -sh / 2 }, + { x: -sw / 2 - 0.2, y: -sh / 2 }, + ], + stroke_width: 0.08, + }) + + const silkscreenRefText: SilkscreenRef = silkscreenRef(0, sh / 2 + 0.3, 0.2) + + const courtyard: PcbCourtyardRect = { + type: "pcb_courtyard_rect", + pcb_courtyard_rect_id: "", + pcb_component_id: "", + center: { x: 0, y: 0 }, + width: w + pl + 0.3, + height: p + pw + 0.5, + layer: "top", + } + + return { + circuitJson: [ + ...pads, + silkscreenRefText as AnyCircuitElement, + ...silkscreenPaths, + courtyard, + ], + parameters, + } +} diff --git a/src/footprinter.ts b/src/footprinter.ts index fa51a3b4..e33f28bc 100644 --- a/src/footprinter.ts +++ b/src/footprinter.ts @@ -40,6 +40,9 @@ export type Footprinter = { dip: ( num_pins?: number, ) => FootprinterParamsBuilder<"w" | "p" | "id" | "od" | "wide" | "narrow"> + utdfn: ( + num_pins?: number, + ) => FootprinterParamsBuilder<"w" | "p" | "pl" | "pw" | "epw" | "eph"> cap: () => FootprinterParamsBuilder res: () => FootprinterParamsBuilder diode: () => FootprinterParamsBuilder @@ -275,6 +278,8 @@ const normalizeDefinition = (def: string): string => { .replace(/^sot-223-(\d+)(?=_|$)/i, "sot223_$1") .replace(/^to-220f-(\d+)(?=_|$)/i, "to220f_$1") .replace(/^jst_(ph|sh|zh)_(\d+)(?=_|$)/i, "jst$2_$1") + .replace(/^utdfn-4-ep\(1x1\)(?=_|$)/i, "utdfn4") + .replace(/^utdfn-4-ep(?=_|$)/i, "utdfn4") } export const string = (def: string): Footprinter => { diff --git a/tests/__snapshots__/utdfn-4-ep-1x1.snap.svg b/tests/__snapshots__/utdfn-4-ep-1x1.snap.svg new file mode 100644 index 00000000..817f935c --- /dev/null +++ b/tests/__snapshots__/utdfn-4-ep-1x1.snap.svg @@ -0,0 +1 @@ +{REF} \ No newline at end of file diff --git a/tests/utdfn.test.ts b/tests/utdfn.test.ts new file mode 100644 index 00000000..5e805db3 --- /dev/null +++ b/tests/utdfn.test.ts @@ -0,0 +1,46 @@ +import { expect, test } from "bun:test" +import { convertCircuitJsonToPcbSvg } from "circuit-to-svg" +import { fp } from "../src/footprinter" + +test("utdfn-4-ep(1x1)", () => { + const circuitJson = fp.string("utdfn-4-ep(1x1)").circuitJson() + + // Find all SMD pads + const pads = circuitJson.filter((e: any) => e.type === "pcb_smtpad") + expect(pads.length).toBe(5) + + // Verify exposed pad (pad 5) is at center + const ep = pads.find((p: any) => p.port_hints?.includes("5")) + expect(ep).toBeDefined() + expect(ep.x).toBe(0) + expect(ep.y).toBe(0) + expect(ep.width).toBeCloseTo(0.48, 3) + expect(ep.height).toBeCloseTo(0.48, 3) + + // Verify contact pad 1 (bottom-left) + const pad1 = pads.find((p: any) => p.port_hints?.includes("1")) + expect(pad1).toBeDefined() + expect(pad1.x).toBeCloseTo(-0.45, 3) + expect(pad1.y).toBeCloseTo(-0.325, 3) + + // Verify contact pad 2 (top-left) + const pad2 = pads.find((p: any) => p.port_hints?.includes("2")) + expect(pad2).toBeDefined() + expect(pad2.x).toBeCloseTo(-0.45, 3) + expect(pad2.y).toBeCloseTo(0.325, 3) + + // Verify contact pad 3 (top-right) + const pad3 = pads.find((p: any) => p.port_hints?.includes("3")) + expect(pad3).toBeDefined() + expect(pad3.x).toBeCloseTo(0.45, 3) + expect(pad3.y).toBeCloseTo(0.325, 3) + + // Verify contact pad 4 (bottom-right) + const pad4 = pads.find((p: any) => p.port_hints?.includes("4")) + expect(pad4).toBeDefined() + expect(pad4.x).toBeCloseTo(0.45, 3) + expect(pad4.y).toBeCloseTo(-0.325, 3) + + const svgContent = convertCircuitJsonToPcbSvg(circuitJson) + expect(svgContent).toMatchSvgSnapshot(import.meta.path, "utdfn-4-ep-1x1") +})