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
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,27 @@ fp().sod123w().p("3.4mm").pw("0.95mm").cathodepin(1)
fp().sod123w().p("3.4mm").pw("0.95mm").anodepin(1)
```

### SOIC body dimensions

For SOIC footprints, `bodywidth` and `bodyheight` set the physical body dimensions
used to generate the courtyard, independently of the pad positions and sizes.
Each is optional; omitted dimensions retain their existing inferred values.
These options do not resize the silkscreen.

```ts
fp.string(
"soic8_w6.9mm_pl1.95mm_pw0.6mm_p1.27mm_bodywidth3.9mm_bodyheight4.9mm",
)

// Builder equivalent:
fp().soic(8).w("6.9mm").pl("1.95mm").pw("0.6mm").p("1.27mm")
.bodywidth("3.9mm").bodyheight("4.9mm")
```

The courtyard encloses the body and copper envelopes with 0.25mm clearance.
In this example, `w` is the total pad-tip span because `legsoutside` is unset;
it is separate from the 3.9mm physical body width.

### Rounded pads

Every footprint accepts a `rounded${radius}` modifier that applies the requested
Expand Down
101 changes: 85 additions & 16 deletions src/fn/soic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,22 @@ export const extendSoicDef = (newDefaults: {
p: length.default(length.parse(newDefaults.p ?? "1.27mm")),
pw: length.default(length.parse(newDefaults.pw ?? "0.6mm")),
pl: length.default(length.parse(newDefaults.pl ?? "1.0mm")),
bodywidth: length
.refine((value) => Number.isFinite(value) && value > 0, {
message: "bodywidth must be a positive finite length",
})
.optional()
.describe(
"physical body width used for the courtyard, independent of pad placement",
),
bodyheight: length
.refine((value) => Number.isFinite(value) && value > 0, {
message: "bodyheight must be a positive finite length",
})
.optional()
.describe(
"physical body height used for the courtyard, independent of the pin span",
),
legsoutside: z
.boolean()
.optional()
Expand Down Expand Up @@ -110,6 +126,8 @@ export const soic = (raw_params: {
soic: true
num_pins: number
w: number
bodywidth?: string | number
bodyheight?: string | number
p?: number
id?: string | number
od?: string | number
Expand Down Expand Up @@ -184,8 +202,8 @@ export const soicWithoutParsing = (parameters: z.infer<typeof soic_def>) => {
{ x: -sw / 2, y: -sh / 2 },
],
}
const bodyHalfWidth = parameters.w / 2
const bodyHalfHeight = sh / 2
const bodyHalfWidth = (parameters.bodywidth ?? parameters.w) / 2
const bodyHalfHeight = (parameters.bodyheight ?? sh) / 2

// Outer rect: wide (pad tips in X), short (pin span in Y)
const courtyardStepOuterHalfWidth =
Expand All @@ -197,25 +215,76 @@ export const soicWithoutParsing = (parameters: z.infer<typeof soic_def>) => {
Math.min(maxPadExtentX, bodyHalfWidth) + 0.25
const courtyardStepOuterHalfHeight =
Math.max(maxPadExtentY, bodyHalfHeight) + 0.25
let courtyardOutline = createRectUnionOutline([
{
minX: -courtyardStepOuterHalfWidth,
maxX: courtyardStepOuterHalfWidth,
minY: -courtyardStepInnerHalfHeight,
maxY: courtyardStepInnerHalfHeight,
},
{
minX: -courtyardStepInnerHalfWidth,
maxX: courtyardStepInnerHalfWidth,
minY: -courtyardStepOuterHalfHeight,
maxY: courtyardStepOuterHalfHeight,
},
])

if (
parameters.bodywidth !== undefined ||
parameters.bodyheight !== undefined
) {
// Use the actual body and copper envelopes. Combining the minimum X of
// one with the minimum Y of the other can cut corners off a larger body.
// Include an offset thermal pad in the copper envelope as well.
if (parameters.thermalpad) {
maxPadExtentX = Math.max(
maxPadExtentX,
Math.abs(parameters.thermalpadcenteroffsetx) +
parameters.thermalpad.x / 2,
)
maxPadExtentY = Math.max(
maxPadExtentY,
Math.abs(parameters.thermalpadcenteroffsety) +
parameters.thermalpad.y / 2,
)
}
const envelopes = [
{ halfWidth: maxPadExtentX + 0.25, halfHeight: maxPadExtentY + 0.25 },
{ halfWidth: bodyHalfWidth + 0.25, halfHeight: bodyHalfHeight + 0.25 },
].sort((a, b) => b.halfWidth - a.halfWidth)
const wide = envelopes[0]!
const narrow = envelopes[1]!
if (
wide.halfHeight >= narrow.halfHeight ||
wide.halfWidth - narrow.halfWidth < 1e-9
) {
// A contained envelope (or a floating-point-width sliver) needs no step.
// Taking the maximum height keeps both envelopes entirely inside.
const halfHeight = Math.max(wide.halfHeight, narrow.halfHeight)
courtyardOutline = [
{ x: -wide.halfWidth, y: halfHeight },
{ x: wide.halfWidth, y: halfHeight },
{ x: wide.halfWidth, y: -halfHeight },
{ x: -wide.halfWidth, y: -halfHeight },
]
} else {
courtyardOutline = createRectUnionOutline(
envelopes.map(({ halfWidth, halfHeight }) => ({
minX: -halfWidth,
maxX: halfWidth,
minY: -halfHeight,
maxY: halfHeight,
})),
)
}
}
const courtyard: PcbCourtyardOutline = {
type: "pcb_courtyard_outline",
pcb_courtyard_outline_id: "",
pcb_component_id: "",
layer: "top",
outline: createRectUnionOutline([
{
minX: -courtyardStepOuterHalfWidth,
maxX: courtyardStepOuterHalfWidth,
minY: -courtyardStepInnerHalfHeight,
maxY: courtyardStepInnerHalfHeight,
},
{
minX: -courtyardStepInnerHalfWidth,
maxX: courtyardStepInnerHalfWidth,
minY: -courtyardStepOuterHalfHeight,
maxY: courtyardStepOuterHalfHeight,
},
]),
outline: courtyardOutline,
}

return [
Expand Down
2 changes: 2 additions & 0 deletions src/footprinter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@ export type Footprinter = {
num_pins?: number,
) => FootprinterParamsBuilder<
| "w"
| "bodywidth"
| "bodyheight"
| "p"
| "pw"
| "pl"
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading