Skip to content
61 changes: 61 additions & 0 deletions scripts/verify_utdfn.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
require("esbuild-register")
const { fp } = require("../src/footprinter")
const assert = require("assert")

console.log("=== Verifying UTDFN-4-EP(1x1) Footprint ===")

try {
const circuitJson = fp.string("UTDFN-4-EP(1x1)").circuitJson()

// Verify total elements
console.log(`Total circuit elements: ${circuitJson.length}`)

// Filter pads
const pads = circuitJson.filter((x) => x.type === "pcb_pad")
console.log(`Found ${pads.length} pads`)
assert.strictEqual(
pads.length,
5,
"Should have exactly 5 pads (4 pins + 1 EP)",
)

// Verify EP pad (pin 5) is at center (0, 0)
const epPad = pads.find((x) => x.pcb_pad_id === "5")
assert.ok(epPad, "Pin 5 (Exposed Pad) should exist")
assert.strictEqual(epPad.x, 0, "EP x coordinate must be 0")
assert.strictEqual(epPad.y, 0, "EP y coordinate must be 0")
assert.strictEqual(epPad.width, 0.5, "EP width should be 0.5mm")
assert.strictEqual(epPad.height, 0.5, "EP height should be 0.5mm")

// Verify regular pads (pins 1 to 4)
const pad1 = pads.find((x) => x.pcb_pad_id === "1")
assert.ok(pad1, "Pin 1 should exist")
// dfn4 width is 1.0mm, pin length pl is 0.3mm.
// getCcwSoicCoords logic: widthincludeslegs is true, so legsoutside is false, so legoffset = -pl/2 = -0.15mm
// Left side pins (1 & 2): x = -w/2 - legoffset = -0.5 - (-0.15) = -0.35mm.
assert.strictEqual(
Math.abs(pad1.x - -0.35) < 0.001,
true,
"Pin 1 x coordinate should be -0.35mm",
)
assert.strictEqual(pad1.width, 0.3, "Pin 1 width should be 0.3mm")
assert.strictEqual(pad1.height, 0.25, "Pin 1 height should be 0.25mm")

// Verify alias parity
const canonical = fp
.string(
"dfn4_w1.00mm_h1.00mm_p0.65mm_pl0.30mm_pw0.25mm_ep_epw0.50mm_eph0.50mm",
)
.circuitJson()
assert.deepStrictEqual(
circuitJson,
canonical,
"Alias 'UTDFN-4-EP(1x1)' must yield identical circuit JSON to canonical representation",
)

console.log("\n✅ ALL LOCAL VERIFICATIONS PASSED SUCCESSFULLY!")
} catch (err) {
console.error("\n❌ VERIFICATION FAILED:")
console.error(err)
process.exit(1)
}
75 changes: 75 additions & 0 deletions scripts/verify_utdfn.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { fp } from "../src/footprinter"
import assert from "assert"

console.log("=== Verifying UTDFN-4-EP(1x1) Footprint ===")

try {
const circuitJson = fp.string("UTDFN-4-EP(1x1)").circuitJson()

// Verify total elements
console.log(`Total circuit elements: ${circuitJson.length}`)
console.log("Element types:", circuitJson.map((x) => x.type).join(", "))

// Filter pads (type is pcb_smtpad, not pcb_pad)
const pads = circuitJson.filter((x) => x.type === "pcb_smtpad")
console.log(`Found ${pads.length} pads`)
assert.strictEqual(
pads.length,
5,
"Should have exactly 5 pads (4 pins + 1 EP)",
)

// Find EP pad - it's pin number 5
const epPad: any = pads.find((x: any) => x.port_hints?.includes("5"))
assert.ok(epPad, "Pin 5 (Exposed Pad) should exist")
assert.strictEqual(epPad.x, 0, "EP x coordinate must be 0")
assert.strictEqual(epPad.y, 0, "EP y coordinate must be 0")
assert.ok(Math.abs(epPad.width - 0.5) < 0.001, "EP width should be ~0.5mm")
assert.ok(Math.abs(epPad.height - 0.5) < 0.001, "EP height should be ~0.5mm")

// Verify regular pads (pins 1 to 4)
const pad1: any = pads.find((x: any) => x.port_hints?.includes("1"))
assert.ok(pad1, "Pin 1 should exist")
console.log(
"Pad 1 details:",
JSON.stringify({
x: pad1.x,
y: pad1.y,
width: pad1.width,
height: pad1.height,
}),
)
// dfn4 width is 1.0mm, pin length pl is 0.3mm.
// getCcwSoicCoords: widthincludeslegs=true → legsoutside=false → legoffset = -pl/2 = -0.15mm
// Left side pins: x = -w/2 - legoffset = -0.5 - (-0.15) = -0.35mm
assert.ok(
Math.abs(pad1.x - -0.35) < 0.01,
`Pin 1 x should be ~-0.35mm, got ${pad1.x}`,
)
assert.ok(
Math.abs(pad1.width - 0.3) < 0.01,
`Pin 1 width should be ~0.3mm, got ${pad1.width}`,
)
assert.ok(
Math.abs(pad1.height - 0.25) < 0.01,
`Pin 1 height should be ~0.25mm, got ${pad1.height}`,
)

// Verify alias parity
const canonical = fp
.string(
"dfn4_w1.00mm_h1.00mm_p0.65mm_pl0.30mm_pw0.25mm_ep_epw0.50mm_eph0.50mm",
)
.circuitJson()
assert.deepStrictEqual(
circuitJson,
canonical,
"Alias 'UTDFN-4-EP(1x1)' must yield identical circuit JSON to canonical representation",
)

console.log("\n✅ ALL LOCAL VERIFICATIONS PASSED SUCCESSFULLY!")
} catch (err) {
console.error("\n❌ VERIFICATION FAILED:")
console.error(err)
process.exit(1)
}
52 changes: 41 additions & 11 deletions src/fn/dfn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,51 @@ import type {
PcbCourtyardRect,
PcbSilkscreenPath,
} from "circuit-json"
import {
extendSoicDef,
soicWithoutParsing,
type SoicInput,
getCcwSoicCoords,
} from "./soic"
import { getCcwSoicCoords } from "./soic"
import { rectpad } from "src/helpers/rectpad"
import { z } from "zod"
import { CORNERS } from "src/helpers/corner"
import { type SilkscreenRef, silkscreenRef } from "src/helpers/silkscreenRef"
import { base_def } from "../helpers/zod/base_def"
import { length } from "circuit-json"

export const dfn_def = extendSoicDef({})
export const dfn_def = base_def
.extend({
fn: z.string(),
num_pins: z.number().optional().default(8),
w: length.default("5.3mm"),
p: length.default("1.27mm"),
pw: length.default("0.6mm"),
pl: length.default("1.0mm"),
ep: z.boolean().optional().default(false),
epw: length.optional(),
eph: length.optional(),
silkscreen_stroke_width: z.number().optional().default(0.1),
})
.transform((v) => {
if (!v.pw && !v.pl) {
v.pw = 0.6
v.pl = 1.0
} else if (!v.pw) {
v.pw = v.pl! * (0.6 / 1.0)
} else if (!v.pl) {
v.pl = v.pw! * (1.0 / 0.6)
}
return v
})

/**
* Dual Flat No-lead
*
* Similar to SOIC but different silkscreen
*/
export const dfn = (
raw_params: SoicInput,
raw_params: any,
): { circuitJson: AnyCircuitElement[]; parameters: any } => {
if (raw_params.string && raw_params.string.includes("_ep")) {
raw_params.ep = true
}

const parameters = dfn_def.parse(raw_params)
const pads: AnyCircuitElement[] = []
for (let i = 0; i < parameters.num_pins; i++) {
Expand All @@ -35,9 +59,15 @@ export const dfn = (
pl: parameters.pl,
widthincludeslegs: true,
})
pads.push(
rectpad(i + 1, x, y, parameters.pl ?? "1mm", parameters.pw ?? "0.6mm"),
)
pads.push(rectpad(i + 1, x, y, parameters.pl, parameters.pw))
}

if (parameters.ep) {
const epw = parameters.epw ?? parameters.w * 0.5
const eph =
parameters.eph ??
((parameters.num_pins / 2 - 1) * parameters.p + parameters.pw) * 0.5
pads.push(rectpad(parameters.num_pins + 1, 0, 0, epw, eph))
}

// The silkscreen is 4 corners and an arrow identifier for pin1
Expand Down
13 changes: 8 additions & 5 deletions src/fn/jst.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,16 +245,19 @@ function generateSilkscreenBody({
p?: number
}): PcbSilkscreenPath {
if (variant === "ph") {
const pinSpan = numPins && p ? (numPins - 1) * p : 2.2
const bodyLeft = -pinSpan / 2 - 1.9
const bodyRight = pinSpan / 2 + 1.9
return {
type: "pcb_silkscreen_path",
layer: "top",
pcb_component_id: "",
route: [
{ x: -3, y: 3 },
{ x: 3, y: 3 },
{ x: 3, y: -2 },
{ x: -3, y: -2 },
{ x: -3, y: 3 },
{ x: bodyLeft, y: 3 },
{ x: bodyRight, y: 3 },
{ x: bodyRight, y: -2 },
{ x: bodyLeft, y: -2 },
{ x: bodyLeft, y: 3 },
],
stroke_width: 0.1,
pcb_silkscreen_path_id: "",
Expand Down
7 changes: 7 additions & 0 deletions src/footprinter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,10 +271,17 @@ const normalizeDefinition = (def: string): string => {
return def
.trim()
.replace(/^pinheader(?=[\d_]|$)/i, "pinrow")
.replace(/^pdip[-_]?(\d+)/i, "dip$1")
.replace(/^spdip[-_]?(\d+)/i, "dip$1_p1.778mm")
.replace(/^sot23-(\d+)(?=_|$)/i, "sot23_$1")
.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,
"dfn4_w1.00mm_h1.00mm_p0.65mm_pl0.30mm_pw0.25mm_ep_epw0.50mm_eph0.50mm",
)
.replace(/^utdfn(?=[\d_]|$)/i, "dfn")
}

export const string = (def: string): Footprinter => {
Expand Down
2 changes: 1 addition & 1 deletion tests/__snapshots__/jst.test.tsjst_ph_4.snap.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion tests/__snapshots__/pad.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion tests/__snapshots__/pad_3x2.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion tests/__snapshots__/smtpad_circle_d1.2.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading