Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 39 additions & 8 deletions src/fn/qfn.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,49 @@
import type { AnySoupElement } from "circuit-json"
import { base_quad_def, quad, quad_def, quadTransform } from "./quad"
import type { z } from "zod"
import { base_quad_def, quad, quadTransform, quad_def } from "./quad"

export const qfn_def = base_quad_def.extend({}).transform(quadTransform)

export const qfn = (
parameters: z.input<typeof qfn_def>,
raw_params: z.input<typeof qfn_def>,
): { circuitJson: AnySoupElement[]; parameters: any } => {
parameters.legsoutside = false
if (!parameters.pl) {
parameters.pl = 0.875
raw_params.legsoutside = false
const pitchValue =
typeof raw_params.p === "string"
? Number.parseFloat(raw_params.p)
: raw_params.p
if (raw_params.pw === undefined && raw_params.pl === undefined) {
if (pitchValue !== undefined && pitchValue > 0) {
// IPC-compliant defaults: pw = 0.5 * pitch, pl based on standard QFN sizing
raw_params.pw = pitchValue * 0.5
raw_params.pl = 0.875
} else {
raw_params.pl = 0.875
raw_params.pw = 0.25
}
} else {
if (raw_params.pl === undefined) {
raw_params.pl = 0.875
}
if (raw_params.pw === undefined) {
raw_params.pw =
pitchValue !== undefined && pitchValue > 0 ? pitchValue * 0.5 : 0.25
}
Comment on lines +24 to +31
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant pitch value calculation and parameter assignment. When raw_params.pw is provided but raw_params.pl is not (or vice versa), the code recalculates pitchValue (lines 25-28) but this is already done in the first condition block (lines 12-15). More critically, if the user explicitly provides raw_params.pw, it should be respected without modification. However, the current logic in the else block (lines 32-33) will still potentially override pw if it's falsy (0, null, undefined, etc). A user passing pw: 0 intentionally would have it replaced with either pitchValue * 0.5 or 0.25.

// This overwrites pw even if explicitly set to 0
if (!raw_params.pw) {
  raw_params.pw = pitchValue ? pitchValue * 0.5 : 0.25
}

The condition !raw_params.pw treats 0 as falsy and would replace it. Should use explicit undefined checks instead:

if (raw_params.pw === undefined) {
  raw_params.pw = pitchValue ? pitchValue * 0.5 : 0.25
}

Spotted by Graphite Agent

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

}
if (!parameters.pw) {
parameters.pw = 0.25

// When body dimensions (w/h) are explicitly specified and padoffset is not,
// compute padoffset so pad centers match IPC-7351B / KiCad positioning.
// QFN pads extend slightly beyond the package body edge; the pad center
// sits approximately 0.0625mm inside the body edge.
if (raw_params.padoffset === undefined && raw_params.w !== undefined) {
const pl =
typeof raw_params.pl === "string"
? Number.parseFloat(raw_params.pl)
: raw_params.pl
if (pl !== undefined && pl > 0) {
raw_params.padoffset = 0.0625 - pl / 2
}
}
return quad(parameters)

return quad(raw_params)
}
15 changes: 10 additions & 5 deletions src/fn/quad.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export const base_quad_def = base_def.extend({
pl: length.optional(),
thermalpad: z.union([z.literal(true), dim2d]).optional(),
legsoutside: z.boolean().default(false),
padoffset: z.number().optional(),
})

export const quadTransform = <T extends z.infer<typeof base_quad_def>>(
Expand Down Expand Up @@ -81,8 +82,9 @@ export const getQuadCoords = (params: {
p: number // pitch between pins
pl: number // length of the pin
legsoutside?: boolean
padoffset?: number
}) => {
const { pin_count, pn, w, h, p, pl, legsoutside } = params
const { pin_count, pn, w, h, p, pl, legsoutside, padoffset } = params
const sidePinCount = pin_count / 4
const side = SIDES_CCW[Math.floor((pn - 1) / sidePinCount)]
const pos = (pn - 1) % sidePinCount
Expand All @@ -95,15 +97,17 @@ export const getQuadCoords = (params: {
/** pad center distance from edge (negative is inside, positive is outside) */
const pcdfe = legsoutside ? pl / 2 : -pl / 2

const offset = padoffset ?? 0.1

switch (side) {
case "left":
return { x: -w / 2 - pcdfe + 0.1, y: ibh / 2 - pos * p, o: "vert" }
return { x: -w / 2 - pcdfe + offset, y: ibh / 2 - pos * p, o: "vert" }
case "bottom":
return { x: -ibw / 2 + pos * p, y: -h / 2 - pcdfe + 0.1, o: "horz" }
return { x: -ibw / 2 + pos * p, y: -h / 2 - pcdfe + offset, o: "horz" }
case "right":
return { x: w / 2 + pcdfe - 0.1, y: -ibh / 2 + pos * p, o: "vert" }
return { x: w / 2 + pcdfe - offset, y: -ibh / 2 + pos * p, o: "vert" }
case "top":
return { x: ibw / 2 - pos * p, y: h / 2 + pcdfe - 0.1, o: "horz" }
return { x: ibw / 2 - pos * p, y: h / 2 + pcdfe - offset, o: "horz" }
default:
throw new Error("Invalid pin number")
}
Expand All @@ -130,6 +134,7 @@ export const quad = (
p: parameters.p ?? 0.5,
pl: parameters.pl,
legsoutside: parameters.legsoutside,
padoffset: parameters.padoffset,
})

let pw = parameters.pw
Expand Down
1 change: 1 addition & 0 deletions tests/__snapshots__/qfn32_w5_h5.snap.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions tests/__snapshots__/qfn32_w5_h5_p0.5_thermalpad.snap.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading