diff --git a/README.md b/README.md index 1c799d2a..3c4e668a 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,8 @@ sot23 qfn24_w6_h6_p0.8mm_thermalpad_startingpin(topside,rightpin)_ccw qfn64_thermalpad6.3mmx6.3mm_thermalvias4x4_thermalviapitch1mm_thermalviaid0.3048mm_thermalviaod0.6096mm axial_p0.2in +do219ad +sod323he ``` You can use these like so: @@ -113,6 +115,25 @@ fp().sod123w().p("3.4mm").pw("0.95mm").cathodepin(1) fp().sod123w().p("3.4mm").pw("0.95mm").anodepin(1) ``` +### Explicit two-pad package identity + +Two rectangular pads alone do not identify the component package. Use a named +standard-family footprint when downstream tools such as a 3D renderer need an +unambiguous package identity: + +```ts +fp.string("do219ad") +fp.string("sod323he") +fp.string("dfn2_w1.6mm_pl0.6mm_pw0.6mm") +``` + +`do-219ad` and `sod-323he` are accepted aliases and normalize to the canonical +names above. DO-219AD and SOD-323HE assign pin 1 to the cathode at negative X +and pin 2 to the anode at positive X. Their land-pattern parameters (`p`, `pw`, +and `ph`) are independent from their validated mechanical parameters such as +`bodylength`, `bodywidth`, and `bodyheight`. A generic `smdpads2` footprint +remains generic and never selects one of these packages by pad dimensions. + ### Rounded pads Every footprint accepts a `rounded${radius}` modifier that applies the requested diff --git a/src/fn/dfn.ts b/src/fn/dfn.ts index af564412..a51b6382 100644 --- a/src/fn/dfn.ts +++ b/src/fn/dfn.ts @@ -4,18 +4,42 @@ import type { PcbSilkscreenPath, } from "circuit-json" import { length } from "circuit-json" -import { extendSoicDef, getCcwSoicCoords } from "./soic" -import { rectpad } from "src/helpers/rectpad" -import { pillpad } from "src/helpers/pillpad" -import { z } from "zod" import { CORNERS } from "src/helpers/corner" -import { type SilkscreenRef, silkscreenRef } from "src/helpers/silkscreenRef" -import { function_call } from "src/helpers/zod/function-call" import { createThermalPad } from "src/helpers/create-thermal-pad" import { addThermalVias, thermalViaDef } from "src/helpers/create-thermal-vias" +import { pillpad } from "src/helpers/pillpad" import { polygonpad } from "src/helpers/polygonpad" +import { rectpad } from "src/helpers/rectpad" +import { type SilkscreenRef, silkscreenRef } from "src/helpers/silkscreenRef" +import { function_call } from "src/helpers/zod/function-call" +import { z } from "zod" +import { extendSoicDef, getCcwSoicCoords } from "./soic" -export const dfn_def = extendSoicDef({}).and(thermalViaDef) +const positiveMechanicalLength = length.refine((value) => value > 0, { + message: "DFN mechanical dimension must be positive", +}) +const nonnegativeMechanicalLength = length.refine((value) => value >= 0, { + message: "DFN mechanical dimension must be non-negative", +}) + +/** Optional physical-package dimensions for an explicitly named DFN model. */ +export const dfn_mechanical_def = z.object({ + bodywidth: positiveMechanicalLength.optional(), + bodylength: positiveMechanicalLength.optional(), + bodythickness: positiveMechanicalLength.optional(), + standoff: nonnegativeMechanicalLength.optional(), + terminalinset: nonnegativeMechanicalLength.optional(), + terminallength: positiveMechanicalLength.optional(), + terminalwidth: positiveMechanicalLength.optional(), + terminalpitch: positiveMechanicalLength.optional(), + terminalthickness: positiveMechanicalLength.optional(), + pin1terminalchamfer: nonnegativeMechanicalLength.optional(), + pin1markwidth: nonnegativeMechanicalLength.optional(), +}) + +export const dfn_def = extendSoicDef({}) + .and(thermalViaDef) + .and(dfn_mechanical_def) export type DfnInput = z.input & { /** Replace the four rectangular DFN pads with chamfered corner pads. */ cornerpads?: boolean @@ -53,6 +77,27 @@ export const dfn = ( cornerpadcutlength, missing: missingPositions, } + if ( + parameters.bodythickness !== undefined && + parameters.standoff !== undefined && + parameters.standoff >= parameters.bodythickness + ) { + throw new Error("DFN standoff must be less than bodythickness") + } + if ( + parameters.terminalthickness !== undefined && + parameters.bodythickness !== undefined && + parameters.terminalthickness > parameters.bodythickness + ) { + throw new Error("DFN terminalthickness must not exceed bodythickness") + } + if ( + parameters.pin1markwidth !== undefined && + parameters.bodywidth !== undefined && + parameters.pin1markwidth >= parameters.bodywidth / 2 + ) { + throw new Error("DFN pin1markwidth must be less than half bodywidth") + } const nominalPinCount = parameters.num_pins if ( missingPositions.some( diff --git a/src/fn/do219ad.ts b/src/fn/do219ad.ts new file mode 100644 index 00000000..a79bd342 --- /dev/null +++ b/src/fn/do219ad.ts @@ -0,0 +1,41 @@ +import type { AnyCircuitElement } from "circuit-json" +import type { z } from "zod" +import { + createStandardFlatLeadDiodeCircuitJson, + createStandardFlatLeadDiodeDef, +} from "../helpers/standard-flat-lead-diode" + +/** + * JEDEC DO-219AD (MicroSMP), using nominal Vishay package dimensions. + * Dimensional reference: https://www.vishay.com/doc/?89019= + */ +export const do219ad_def = createStandardFlatLeadDiodeDef("do219ad", { + p: "1.84mm", + pw: "1.35mm", + ph: "0.95mm", + bodylength: "2.2mm", + bodywidth: "1.3mm", + bodyheight: "0.68mm", + leadspan: "2.5mm", + cathodelength: "1.3mm", + cathodewidth: "0.88mm", + anodelength: "0.65mm", + anodewidth: "0.65mm", + terminalthickness: "0.195mm", + standoff: "0.11mm", + taperinset: "0.05mm", + markingwidth: "0.23mm", +}) + +export const do219ad = ( + rawParameters: z.input, +): { + circuitJson: AnyCircuitElement[] + parameters: z.output +} => { + const parameters = do219ad_def.parse(rawParameters) + return { + circuitJson: createStandardFlatLeadDiodeCircuitJson(parameters), + parameters, + } +} diff --git a/src/fn/index.ts b/src/fn/index.ts index 08c0735d..37566bbd 100644 --- a/src/fn/index.ts +++ b/src/fn/index.ts @@ -22,6 +22,7 @@ export { sot886 } from "./sot886" export { sot23 } from "./sot23" export { sot25 } from "./sot25" export { dfn } from "./dfn" +export { do219ad } from "./do219ad" export { pinrow } from "./pinrow" export { headermodule } from "./headermodule" export { sot563 } from "./sot563" @@ -46,6 +47,7 @@ export { sop8 } from "./sop8" export { sod80 } from "./sod80" export { sod123w } from "./sod123w" export { sod323 } from "./sod323" +export { sod323he } from "./sod323he" export { sod923 } from "./sod923" export { sod882 } from "./sod882" export { sod323f } from "./sod323f" diff --git a/src/fn/sod323he.ts b/src/fn/sod323he.ts new file mode 100644 index 00000000..bb46a57e --- /dev/null +++ b/src/fn/sod323he.ts @@ -0,0 +1,41 @@ +import type { AnyCircuitElement } from "circuit-json" +import type { z } from "zod" +import { + createStandardFlatLeadDiodeCircuitJson, + createStandardFlatLeadDiodeDef, +} from "../helpers/standard-flat-lead-diode" + +/** + * JEITA SOD-323HE, using nominal ROHM package dimensions. + * Dimensional reference: https://www.rohm.com/products/diodes/fast-recovery-diodes/standard/rfu02vsm6s-product + */ +export const sod323he_def = createStandardFlatLeadDiodeDef("sod323he", { + p: "2.1001mm", + pw: "0.8mm", + ph: "1.1mm", + bodylength: "2mm", + bodywidth: "1.4mm", + bodyheight: "0.6mm", + leadspan: "2.5mm", + cathodelength: "0.55mm", + cathodewidth: "0.8mm", + anodelength: "0.55mm", + anodewidth: "0.8mm", + terminalthickness: "0.17mm", + standoff: "0.05mm", + taperinset: "0.15mm", + markingwidth: "0.2mm", +}) + +export const sod323he = ( + rawParameters: z.input, +): { + circuitJson: AnyCircuitElement[] + parameters: z.output +} => { + const parameters = sod323he_def.parse(rawParameters) + return { + circuitJson: createStandardFlatLeadDiodeCircuitJson(parameters), + parameters, + } +} diff --git a/src/footprinter.ts b/src/footprinter.ts index 2302c3bf..10c78e9d 100644 --- a/src/footprinter.ts +++ b/src/footprinter.ts @@ -214,6 +214,36 @@ export type Footprinter = { | "thermalviaod" | "cornerpads" | "cornerpadcutlength" + | "bodywidth" + | "bodylength" + | "bodythickness" + | "standoff" + | "terminalinset" + | "terminallength" + | "terminalwidth" + | "terminalpitch" + | "terminalthickness" + | "pin1terminalchamfer" + | "pin1markwidth" + > + do219ad: () => FootprinterParamsBuilder< + | "p" + | "pw" + | "ph" + | "cyw" + | "cyh" + | "bodylength" + | "bodywidth" + | "bodyheight" + | "leadspan" + | "cathodelength" + | "cathodewidth" + | "anodelength" + | "anodewidth" + | "terminalthickness" + | "standoff" + | "taperinset" + | "markingwidth" > pinrow: ( num_pins?: number, @@ -376,6 +406,25 @@ export type Footprinter = { electrolytic: () => FootprinterParamsBuilder<"d" | "p" | "id" | "od"> sod923: () => FootprinterParamsBuilder<"w" | "h" | "p" | "pl" | "pw"> sod323: () => FootprinterParamsBuilder<"w" | "h" | "p" | "pl" | "pw"> + sod323he: () => FootprinterParamsBuilder< + | "p" + | "pw" + | "ph" + | "cyw" + | "cyh" + | "bodylength" + | "bodywidth" + | "bodyheight" + | "leadspan" + | "cathodelength" + | "cathodewidth" + | "anodelength" + | "anodewidth" + | "terminalthickness" + | "standoff" + | "taperinset" + | "markingwidth" + > sod80: () => FootprinterParamsBuilder<"w" | "h" | "p" | "pl" | "pw"> sod882: () => FootprinterParamsBuilder<"w" | "h" | "p" | "pl" | "pw"> sod882d: () => FootprinterParamsBuilder<"w" | "h" | "p" | "pl" | "pw"> @@ -584,6 +633,8 @@ export type Footprinter = { const normalizeDefinition = (def: string): string => { return def .trim() + .replace(/^do-219ad(?=_|$)/i, "do219ad") + .replace(/^sod-323he(?=_|$)/i, "sod323he") .replace(/^pinheader(?=[\d_]|$)/i, "pinrow") .replace(/^d2pak(\d+)(?=_|$)/i, "d2pak_$1") .replace(/^to-252(?:-(\d+))?(?=_|$)/i, (_, pins) => @@ -632,7 +683,7 @@ export const string = (def: string): Footprinter => { // parameter name. Require another value token after that name so a // normal pitch such as p1mm is still parsed as p + 1mm. const m = s.match( - /((?:p\d+[a-zA-Z]+(?=[\(\d\.\+\-\?]))|[a-zA-Z]+)([\(\d\.\+\-\?].*)?/, + /((?:(?:p\d+|pin1)[a-zA-Z]+(?=[\(\d\.\+\-\?]))|[a-zA-Z]+)([\(\d\.\+\-\?].*)?/, ) if (!m) return null const [, rawFn, v] = m diff --git a/src/helpers/standard-flat-lead-diode.ts b/src/helpers/standard-flat-lead-diode.ts new file mode 100644 index 00000000..f23014e6 --- /dev/null +++ b/src/helpers/standard-flat-lead-diode.ts @@ -0,0 +1,236 @@ +import { + type AnyCircuitElement, + type PcbCourtyardRect, + type PcbSilkscreenPath, + length, +} from "circuit-json" +import { z } from "zod" +import { createFabricationNoteDiodeFromCircuitJson } from "./create-fabrication-note-diode" +import { rectpad } from "./rectpad" +import { silkscreenRef } from "./silkscreenRef" +import { base_def } from "./zod/base_def" + +export type StandardFlatLeadDiodeDefaults = { + p: string + pw: string + ph: string + bodylength: string + bodywidth: string + bodyheight: string + leadspan: string + cathodelength: string + cathodewidth: string + anodelength: string + anodewidth: string + terminalthickness: string + standoff: string + taperinset: string + markingwidth: string +} + +const positiveLength = length.refine((value) => value > 0, { + message: "dimension must be positive", +}) +const nonnegativeLength = length.refine((value) => value >= 0, { + message: "dimension must be non-negative", +}) +const diodePin = z.coerce.number().pipe(z.union([z.literal(1), z.literal(2)])) + +/** + * Defines a named two-lead standard package. Land-pattern dimensions (`p`, + * `pw`, and `ph`) remain independent of the mechanical dimensions consumed by + * 3D renderers. + */ +export const createStandardFlatLeadDiodeDef = ( + name: TName, + defaults: StandardFlatLeadDiodeDefaults, +) => + base_def + .extend({ + [name]: z.literal(true).optional(), + fn: z.literal(name), + string: z.string().optional(), + origin: z.string().optional(), + num_pins: z.literal(2).default(2), + p: positiveLength + .default(length.parse(defaults.p)) + .describe("pad center-to-center pitch"), + pw: positiveLength + .default(length.parse(defaults.pw)) + .describe("pad size along the package length"), + ph: positiveLength + .default(length.parse(defaults.ph)) + .describe("pad size along the package width"), + cyw: positiveLength.optional().describe("courtyard width"), + cyh: positiveLength.optional().describe("courtyard height"), + bodylength: positiveLength.default(length.parse(defaults.bodylength)), + bodywidth: positiveLength.default(length.parse(defaults.bodywidth)), + bodyheight: positiveLength.default(length.parse(defaults.bodyheight)), + leadspan: positiveLength.default(length.parse(defaults.leadspan)), + cathodelength: positiveLength.default( + length.parse(defaults.cathodelength), + ), + cathodewidth: positiveLength.default(length.parse(defaults.cathodewidth)), + anodelength: positiveLength.default(length.parse(defaults.anodelength)), + anodewidth: positiveLength.default(length.parse(defaults.anodewidth)), + terminalthickness: positiveLength.default( + length.parse(defaults.terminalthickness), + ), + standoff: nonnegativeLength.default(length.parse(defaults.standoff)), + taperinset: nonnegativeLength.default(length.parse(defaults.taperinset)), + markingwidth: nonnegativeLength.default( + length.parse(defaults.markingwidth), + ), + cathodepin: diodePin.default(1), + anodepin: diodePin.default(2), + }) + .strict() + .superRefine((parameters, ctx) => { + if (parameters.cathodepin === parameters.anodepin) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "anode and cathode must use different pins", + path: ["cathodepin"], + }) + } + if (parameters.bodyheight <= parameters.standoff) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "bodyheight must be greater than standoff", + path: ["bodyheight"], + }) + } + if (parameters.terminalthickness >= parameters.bodyheight) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "terminalthickness must be less than bodyheight", + path: ["terminalthickness"], + }) + } + if (parameters.standoff >= parameters.terminalthickness) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "standoff must be less than terminalthickness", + path: ["standoff"], + }) + } + if (parameters.leadspan < parameters.bodylength) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "leadspan must be at least bodylength", + path: ["leadspan"], + }) + } + if ( + parameters.taperinset * 2 >= + Math.min(parameters.bodylength, parameters.bodywidth) + ) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "taperinset is too large for the body dimensions", + path: ["taperinset"], + }) + } + if (parameters.markingwidth > parameters.bodylength) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "markingwidth must not exceed bodylength", + path: ["markingwidth"], + }) + } + const terminalOverhang = (parameters.leadspan - parameters.bodylength) / 2 + if ( + parameters.cathodelength <= terminalOverhang || + parameters.anodelength <= terminalOverhang || + parameters.cathodelength + parameters.anodelength >= + parameters.leadspan || + parameters.cathodewidth > parameters.bodywidth || + parameters.anodewidth > parameters.bodywidth + ) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "terminal dimensions are incompatible with the package body", + path: ["leadspan"], + }) + } + if ( + parameters.markingwidth * 2 >= + parameters.bodylength - parameters.taperinset * 2 + ) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "markingwidth is too large for the tapered body", + path: ["markingwidth"], + }) + } + }) + .transform((parameters) => { + const { + [name]: _selector, + string: _string, + ...publicParameters + } = parameters + return publicParameters + }) + +type StandardFlatLeadDiodeParameters = { + num_pins: 2 + p: number + pw: number + ph: number + cyw?: number + cyh?: number + bodylength: number + bodywidth: number + cathodepin: 1 | 2 + anodepin: 1 | 2 +} + +export const createStandardFlatLeadDiodeCircuitJson = ( + parameters: StandardFlatLeadDiodeParameters, +): AnyCircuitElement[] => { + const pads = [ + rectpad(1, -parameters.p / 2, 0, parameters.pw, parameters.ph), + rectpad(2, parameters.p / 2, 0, parameters.pw, parameters.ph), + ] + const bodyHalfLength = parameters.bodylength / 2 + const bodyHalfWidth = parameters.bodywidth / 2 + const silkscreen: PcbSilkscreenPath[] = [bodyHalfWidth, -bodyHalfWidth].map( + (y, index) => ({ + type: "pcb_silkscreen_path", + pcb_silkscreen_path_id: `flat_lead_body_${index}`, + pcb_component_id: "", + layer: "top", + stroke_width: 0.1, + route: [ + { x: -bodyHalfLength, y }, + { x: bodyHalfLength, y }, + ], + }), + ) + const copperHalfLength = parameters.p / 2 + parameters.pw / 2 + const courtyardWidth = + parameters.cyw ?? Math.max(copperHalfLength, bodyHalfLength) * 2 + 0.5 + const courtyardHeight = + parameters.cyh ?? Math.max(parameters.ph / 2, bodyHalfWidth) * 2 + 0.5 + const courtyard: PcbCourtyardRect = { + type: "pcb_courtyard_rect", + pcb_courtyard_rect_id: "", + pcb_component_id: "", + center: { x: 0, y: 0 }, + width: courtyardWidth, + height: courtyardHeight, + layer: "top", + } + + return [ + ...pads, + ...createFabricationNoteDiodeFromCircuitJson(pads, { + cathodePin: parameters.cathodepin, + anodePin: parameters.anodepin, + }), + ...silkscreen, + silkscreenRef(0, courtyardHeight / 2 + 0.4, 0.3), + courtyard, + ] +} diff --git a/src/helpers/zod/AnyFootprinterDefinitionOutput.ts b/src/helpers/zod/AnyFootprinterDefinitionOutput.ts index 6c9e24a8..04be0770 100644 --- a/src/helpers/zod/AnyFootprinterDefinitionOutput.ts +++ b/src/helpers/zod/AnyFootprinterDefinitionOutput.ts @@ -2,6 +2,7 @@ import { axial_def } from "src/fn/axial" import { bga_def } from "src/fn/bga" import { dfn_def } from "src/fn/dfn" import { dip_def } from "src/fn/dip" +import { do219ad_def } from "src/fn/do219ad" import { mlp_def } from "src/fn/mlp" import { ms012_def } from "src/fn/ms012" import { ms013_def } from "src/fn/ms013" @@ -11,6 +12,7 @@ import { qfp_def } from "src/fn/qfp" import { quad_def } from "src/fn/quad" import { smdpinheader_def } from "src/fn/smdpinheader" import { sod_def } from "src/fn/sod123" +import { sod323he_def } from "src/fn/sod323he" import { soic_def } from "src/fn/soic" import { sot23_def } from "src/fn/sot23" import { sot363_def } from "src/fn/sot363" @@ -29,6 +31,7 @@ export const any_footprinter_def = z.union([ axial_def, bga_def, dfn_def, + do219ad_def, dip_def, mlp_def, ms012_def, @@ -39,6 +42,7 @@ export const any_footprinter_def = z.union([ qfp_def, quad_def, sod_def, + sod323he_def, soic_def, sot23_def, sot363_def, diff --git a/tests/__snapshots__/dfn2-explicit-package.snap.svg b/tests/__snapshots__/dfn2-explicit-package.snap.svg new file mode 100644 index 00000000..a22d7fa4 --- /dev/null +++ b/tests/__snapshots__/dfn2-explicit-package.snap.svg @@ -0,0 +1 @@ +{REF} \ No newline at end of file diff --git a/tests/__snapshots__/do219ad.snap.svg b/tests/__snapshots__/do219ad.snap.svg new file mode 100644 index 00000000..cbc2804c --- /dev/null +++ b/tests/__snapshots__/do219ad.snap.svg @@ -0,0 +1 @@ +{REF}+- \ No newline at end of file diff --git a/tests/__snapshots__/sod323he.snap.svg b/tests/__snapshots__/sod323he.snap.svg new file mode 100644 index 00000000..9f0cb64f --- /dev/null +++ b/tests/__snapshots__/sod323he.snap.svg @@ -0,0 +1 @@ +{REF}+- \ No newline at end of file diff --git a/tests/standard-two-pad-packages.test.ts b/tests/standard-two-pad-packages.test.ts new file mode 100644 index 00000000..3de7ecc8 --- /dev/null +++ b/tests/standard-two-pad-packages.test.ts @@ -0,0 +1,135 @@ +import { expect, test } from "bun:test" +import { convertCircuitJsonToPcbSvg } from "circuit-to-svg" +import { fp } from "../src/footprinter" + +const padsOf = (footprint: string) => + fp + .string(footprint) + .circuitJson() + .filter((element) => element.type === "pcb_smtpad") + +test("do219ad identifies the package without changing its established land pattern", () => { + const parameters = fp.string("do219ad").json() + const pads = padsOf("do219ad") + const genericPads = padsOf("smdpads2_p1.84mm_pw1.35mm_ph0.95mm") + + expect(parameters).toMatchObject({ + fn: "do219ad", + num_pins: 2, + cathodepin: 1, + anodepin: 2, + bodylength: 2.2, + bodywidth: 1.3, + bodyheight: 0.68, + }) + expect(parameters).not.toHaveProperty("do219ad") + expect(parameters).not.toHaveProperty("string") + expect(pads).toEqual(genericPads) + + expect( + convertCircuitJsonToPcbSvg(fp.string("do219ad").circuitJson(), { + showCourtyards: true, + }), + ).toMatchSvgSnapshot(import.meta.path, "do219ad") +}) + +test("sod323he identifies the package without changing its established land pattern", () => { + const parameters = fp.string("sod-323he").json() + const pads = padsOf("sod323he") + const genericPads = padsOf("smdpads2_p2.1001mm_pw0.8mm_ph1.1mm") + + expect(parameters).toMatchObject({ + fn: "sod323he", + num_pins: 2, + cathodepin: 1, + anodepin: 2, + bodylength: 2, + bodywidth: 1.4, + bodyheight: 0.6, + }) + expect(pads).toEqual(genericPads) + + expect( + convertCircuitJsonToPcbSvg(fp.string("sod323he").circuitJson(), { + showCourtyards: true, + }), + ).toMatchSvgSnapshot(import.meta.path, "sod323he") +}) + +test("hyphenated standard package aliases normalize to canonical identities", () => { + expect(fp.string("DO-219AD").json().fn).toBe("do219ad") + expect(fp.string("SOD-323HE").json().fn).toBe("sod323he") +}) + +test("standard package builders retain global footprint modifiers", () => { + const pads = fp() + .do219ad() + .origin("pin1") + .circuitJson() + .filter((element) => element.type === "pcb_smtpad") + + expect(pads[0]?.x).toBe(0) + expect(pads[1]?.x).toBeCloseTo(1.84) +}) + +test("standard packages accept validated land and mechanical overrides", () => { + const parameters = fp + .string( + "sod323he_p2.2mm_pw0.85mm_ph1.15mm_bodylength2.1mm_bodywidth1.5mm_bodyheight0.8mm_leadspan2.7mm_standoff0.1mm", + ) + .json() + + expect(parameters).toMatchObject({ + p: 2.2, + pw: 0.85, + ph: 1.15, + bodylength: 2.1, + bodywidth: 1.5, + bodyheight: 0.8, + leadspan: 2.7, + standoff: 0.1, + }) +}) + +test("standard packages reject unknown and invalid parameters", () => { + expect(() => fp.string("do219ad_madeup1mm").json()).toThrow( + "Unrecognized key", + ) + expect(() => + fp.string("do219ad_bodyheight0.1mm_standoff0.2mm").json(), + ).toThrow("bodyheight must be greater than standoff") + expect(() => + fp.string("do219ad_terminalthickness0.1mm_standoff0.1mm").json(), + ).toThrow("standoff must be less than terminalthickness") + expect(() => fp.string("sod323he_cathodepin1_anodepin1").json()).toThrow( + "anode and cathode must use different pins", + ) +}) + +test("dfn2 is an explicit two-lead family and carries mechanical dimensions", () => { + const footprint = + "dfn2_w1.6mm_pl0.6mm_pw0.6mm_bodywidth1mm_bodylength0.6mm_bodythickness0.35mm_standoff0.025mm_terminalinset0.05mm_terminallength0.25mm_terminalwidth0.5mm_terminalpitch0.5mm_terminalthickness0.05mm_pin1terminalchamfer0.03mm_pin1markwidth0.1mm" + const parameters = fp.string(footprint).json() + + expect(parameters).toMatchObject({ + fn: "dfn", + num_pins: 2, + bodywidth: 1, + bodylength: 0.6, + bodythickness: 0.35, + standoff: 0.025, + terminalinset: 0.05, + terminallength: 0.25, + terminalwidth: 0.5, + terminalpitch: 0.5, + terminalthickness: 0.05, + pin1terminalchamfer: 0.03, + pin1markwidth: 0.1, + }) + + expect( + convertCircuitJsonToPcbSvg(fp.string(footprint).circuitJson(), { + showCourtyards: true, + }), + ).toMatchSvgSnapshot(import.meta.path, "dfn2-explicit-package") +})