Skip to content
Closed
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
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Here are some example footprinter strings:
cap0402
res0805
soic8_p1.27mm
soic8_package(D0008A)
dip16
pinrow10
pinrow6_rows2_cols5_p2.54mm_py5.08mm_missing(3,4,8,9)
Expand All @@ -33,6 +34,18 @@ do219ad
sod323he
```

For the TI D0008A SOIC-8 package (for example, TLC555CD), use `soic8_package(D0008A)`
or `fp().soic(8).package("D0008A")`. Its default copper lands follow the example board layout
in the [TLC555 datasheet](https://www.ti.com/lit/ds/symlink/tlc555.pdf),
drawing 4214825/C: 1.55 × 0.60 mm pads, 1.27 mm pitch, 5.40 mm between
pad-row centers, and 0.05 mm corner radius. It reuses the SOIC silkscreen
and courtyard generator; it does not define solder-mask or paste openings.
Explicit dimensions override package defaults; for example,
`soic8_package(D0008A)_pl1.6mm`. The package option requires eight pins.
Use generic `soic8` with explicit dimensions for other land patterns.
Existing `soic8` defaults are unchanged. Common options such as
`soic8_package(D0008A)_norefdes` and `soic8_package(D0008A)_pin1location(topside,left)` also work.

You can use these like so:

```tsx
Expand Down
39 changes: 37 additions & 2 deletions src/fn/soic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,38 @@ export const extendSoicDef = (newDefaults: {
>
})

export const soic_def = extendSoicDef({})
const soicPackage = z.preprocess(
(value) =>
typeof value === "string"
? value.replace(/^\((.*)\)$/, "$1").toUpperCase()
: value,
z.literal("D0008A").optional(),
)

export const soic_def = z.preprocess(
(raw) => {
if (!raw || typeof raw !== "object") return raw
const input = raw as Record<string, unknown>
const selectedPackage = soicPackage.safeParse(input.package)
if (!selectedPackage.success || selectedPackage.data !== "D0008A")
return raw
// TI drawing 4214825/C, example board layout:
// https://www.ti.com/lit/ds/symlink/tlc555.pdf
// w is the outer copper span: 5.4 mm row spacing + 1.55 mm pad length.
return { w: 6.95, p: 1.27, pl: 1.55, pw: 0.6, ...input }
},
extendSoicDef({})
.and(z.object({ package: soicPackage }))
.superRefine((value, ctx) => {
if (value.package === "D0008A" && value.num_pins !== 8) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["num_pins"],
message: "The TI D0008A package requires 8 pins",
})
}
}),
)
export type SoicInput = z.infer<typeof soic_def>

export const getCcwSoicCoords = (parameters: {
Expand Down Expand Up @@ -111,6 +142,7 @@ export const soic = (raw_params: {
num_pins: number
w: number
p?: number
package?: string
id?: string | number
od?: string | number
}): { circuitJson: AnyCircuitElement[]; parameters: SoicInput } => {
Expand All @@ -123,7 +155,10 @@ export const soic = (raw_params: {

export const soicWithoutParsing = (parameters: z.infer<typeof soic_def>) => {
const pads: AnyCircuitElement[] = []
const cornerRadius = Math.min(parameters.pl, parameters.pw) / 8
const cornerRadius =
parameters.package === "D0008A"
? Math.min(0.05, parameters.pl / 2, parameters.pw / 2)
: Math.min(parameters.pl, parameters.pw) / 8
let maxPadExtentX = 0
let maxPadExtentY = 0
for (let i = 0; i < parameters.num_pins; i++) {
Expand Down
1 change: 1 addition & 0 deletions src/footprinter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ export type Footprinter = {
soic: (
num_pins?: number,
) => FootprinterParamsBuilder<
| "package"
| "w"
| "p"
| "pw"
Expand Down
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__/soic8-ti-d0008a.snap.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
95 changes: 95 additions & 0 deletions tests/soic-ti-d0008a.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { expect, test } from "bun:test"
import { convertCircuitJsonToPcbSvg } from "circuit-to-svg"
import { fp } from "../src/footprinter"

test("soic8_package(D0008A) matches TI drawing 4214825/C example copper lands", () => {
const circuit = fp.string("soic8_package(D0008A)").circuitJson()
const pads = circuit.filter((el) => el.type === "pcb_smtpad")
// Independent top-view coordinates from TI's 5.4 mm row spacing and
// 1.27 mm pitch, with counterclockwise numbering starting at top left.
const expected = [
[-2.7, 1.905],
[-2.7, 0.635],
[-2.7, -0.635],
[-2.7, -1.905],
[2.7, -1.905],
[2.7, -0.635],
[2.7, 0.635],
[2.7, 1.905],
]
expect(pads).toHaveLength(8)
pads.forEach((pad, i) => {
expect(pad.shape).toBe("rect")
if (pad.shape !== "rect") throw new Error("Expected rectangular pad")
expect(pad.port_hints).toEqual([String(i + 1)])
expect(pad.layer).toBe("top")
expect(pad.x).toBeCloseTo(expected[i]![0]!, 6)
expect(pad.y).toBeCloseTo(expected[i]![1]!, 6)
expect(pad.width).toBeCloseTo(1.55, 6)
expect(pad.height).toBeCloseTo(0.6, 6)
expect(pad.corner_radius).toBeCloseTo(0.05, 6)
})
expect(convertCircuitJsonToPcbSvg(circuit)).toMatchSvgSnapshot(
import.meta.path,
"soic8-ti-d0008a",
)
})

test("soic8_package(D0008A) supports string and typed builder APIs", () => {
expect(fp().soic(8).package("D0008A").circuitJson()).toEqual(
fp.string("soic8_package(D0008A)").circuitJson(),
)
expect(fp.string("soic8_package(D0008A)").json()).toMatchObject({
fn: "soic",
package: "D0008A",
num_pins: 8,
w: 6.95,
p: 1.27,
pl: 1.55,
pw: 0.6,
})
expect(() => fp.string("soic16_package(D0008A)").circuitJson()).toThrow(
"requires 8 pins",
)
expect(() => fp.string("soic8_package(UNKNOWN)").circuitJson()).toThrow()
expect(fp.string("soic8_package(d0008a)").json()).toEqual(
fp.string("soic8_package(D0008A)").json(),
)
for (const definition of [
"soic8_package(D0008A)_pl1.6mm",
"soic8_pl1.6mm_package(D0008A)",
]) {
expect(fp.string(definition).json()).toMatchObject({
package: "D0008A",
pl: 1.6,
w: 6.95,
})
}
})

test("soic8_package(D0008A) supports common footprint options", () => {
const circuit = fp()
.soic(8)
.package("D0008A")
.norefdes()
.pin1location("rightside", "bottom")
.circuitJson()
expect(circuit).toEqual(
fp
.string("soic8_package(D0008A)_norefdes_pin1location(rightside,bottom)")
.circuitJson(),
)
expect(
circuit
.filter((el) => el.type === "pcb_silkscreen_text")
.every((el) => el.text === ""),
).toBe(true)
const pin1 = circuit.find(
(el) => el.type === "pcb_smtpad" && el.port_hints?.includes("1"),
)
expect(pin1).toMatchObject({ x: 2.7, y: -1.905 })
expect(convertCircuitJsonToPcbSvg(circuit)).toMatchSvgSnapshot(
import.meta.path,
"soic8-ti-d0008a-rightside-norefdes",
)
})
Loading