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
1 change: 1 addition & 0 deletions src/fn/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,4 @@ export { sot343 } from "./sot343"
export { m2host } from "./m2host"
export { mountedpcbmodule } from "./mountedpcbmodule"
export { to92l } from "./to92l"
export { tripad } from "./tripad"
117 changes: 117 additions & 0 deletions src/fn/tripad.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { z } from "zod"
import { rectpad } from "../helpers/rectpad"
import { silkscreenRef } from "../helpers/silkscreenRef"
import type { AnyCircuitElement, PcbSilkscreenPath } from "circuit-json"
import { base_def } from "../helpers/zod/base_def"

/**
* Tripad footprint generator
*
* A tripad is a 3-pad SMD configuration commonly used for transistors, MOSFETs,
* and other 3-terminal devices. It features two smaller signal pads on one side
* and a larger pad (often a thermal/tab pad) on the opposite side.
*
* Default dimensions are based on common tripad footprints found in the wild
* (similar to TO-252 / D-PAK style tripad SMD footprints):
* - w: overall width (pad center-to-center X span + pad widths)
* - h: overall height (body height)
* - p: pitch between the two small pads (Y axis)
* - pw: small pad width (X dimension)
* - ph: small pad height (Y dimension)
* - lw: large pad width (X dimension)
* - lh: large pad height (Y dimension)
*
* Pin numbering (CCW from bottom-left):
* Pin 1: top-left small pad
* Pin 2: large pad (right side) — often drain/tab
* Pin 3: bottom-left small pad
*
* Example string: "tripad", "tripad_w4_h4_p2.3"
*/
export const tripad_def = base_def.extend({
fn: z.string(),
w: z.string().default("4.7mm"),
h: z.string().default("4.0mm"),
p: z.string().default("2.3mm"),
pw: z.string().default("1.5mm"),
ph: z.string().default("1.5mm"),
lw: z.string().default("2.5mm"),
lh: z.string().default("3.5mm"),
string: z.string().optional(),
})

export const tripad = (
raw_params: z.input<typeof tripad_def>,
): { circuitJson: AnyCircuitElement[]; parameters: any } => {
const parameters = tripad_def.parse(raw_params)

const w = Number.parseFloat(parameters.w)
const h = Number.parseFloat(parameters.h)
const p = Number.parseFloat(parameters.p)
const pw = Number.parseFloat(parameters.pw)
const ph = Number.parseFloat(parameters.ph)
const lw = Number.parseFloat(parameters.lw)
const lh = Number.parseFloat(parameters.lh)

// Two small pads on the left side, large pad on the right
// The span from small pad centers to large pad center equals w/2
const smallPadX = -(w / 2 - pw / 2)
const largePadX = w / 2 - lw / 2

const pads: AnyCircuitElement[] = [
// Pin 1: top-left small pad
rectpad(1, smallPadX, p / 2, pw, ph),
// Pin 2: large pad (right side, thermal/tab)
rectpad(2, largePadX, 0, lw, lh),
// Pin 3: bottom-left small pad
rectpad(3, smallPadX, -p / 2, pw, ph),
]

// Silkscreen outline
const silkPadding = 0.2
const bodyLeft = smallPadX - pw / 2 - silkPadding
const bodyRight = largePadX + lw / 2 + silkPadding
const bodyTop = h / 2
const bodyBottom = -h / 2

const silkscreenOutline: PcbSilkscreenPath = {
type: "pcb_silkscreen_path",
layer: "top",
pcb_component_id: "",
pcb_silkscreen_path_id: "silkscreen_outline",
route: [
{ x: bodyLeft, y: bodyTop },
{ x: bodyRight, y: bodyTop },
{ x: bodyRight, y: bodyBottom },
{ x: bodyLeft, y: bodyBottom },
{ x: bodyLeft, y: bodyTop },
],
stroke_width: 0.1,
}

// Pin 1 indicator: notch at top-left corner
const notchSize = 0.4
const pin1Indicator: PcbSilkscreenPath = {
type: "pcb_silkscreen_path",
layer: "top",
pcb_component_id: "",
pcb_silkscreen_path_id: "pin1_indicator",
route: [
{ x: bodyLeft, y: bodyTop - notchSize },
{ x: bodyLeft + notchSize, y: bodyTop },
],
stroke_width: 0.1,
}

const silk = silkscreenRef(0, bodyTop + 0.4, 0.3)

return {
circuitJson: [
...pads,
silkscreenOutline,
pin1Indicator,
silk as AnyCircuitElement,
],
parameters,
}
}
3 changes: 3 additions & 0 deletions src/footprinter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,9 @@ export type Footprinter = {
solderjumper: (
num_pins?: number,
) => FootprinterParamsBuilder<"bridged" | "p" | "pw" | "ph">
tripad: () => FootprinterParamsBuilder<
"w" | "h" | "p" | "pw" | "ph" | "lw" | "lh"
>

params: () => any
/** @deprecated use circuitJson() instead */
Expand Down
1 change: 1 addition & 0 deletions tests/__snapshots__/tripad_custom_call.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__/tripad_custom_dimensions.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__/tripad_default.snap.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions tests/tripad1.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { test, expect } from "bun:test"
import { convertCircuitJsonToPcbSvg } from "circuit-to-svg"
import { fp } from "../src/footprinter"

test("tripad default", () => {
const circuitJson = fp.string("tripad").circuitJson()
const pads = circuitJson.filter((e) => e.type === "pcb_smtpad")
expect(pads.length).toBe(3)
const svgContent = convertCircuitJsonToPcbSvg(circuitJson)
expect(svgContent).toMatchSvgSnapshot(import.meta.path, "tripad_default")
})
14 changes: 14 additions & 0 deletions tests/tripad2.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { test, expect } from "bun:test"
import { convertCircuitJsonToPcbSvg } from "circuit-to-svg"
import { fp } from "../src/footprinter"

test("tripad custom dimensions", () => {
const circuitJson = fp.string("tripad_w5_h4.5_p2.54").circuitJson()
const pads = circuitJson.filter((e) => e.type === "pcb_smtpad")
expect(pads.length).toBe(3)
const svgContent = convertCircuitJsonToPcbSvg(circuitJson)
expect(svgContent).toMatchSvgSnapshot(
import.meta.path,
"tripad_custom_dimensions",
)
})
36 changes: 36 additions & 0 deletions tests/tripad3.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { test, expect } from "bun:test"
import { tripad } from "../src/fn/tripad"

test("tripad pin numbering", () => {
const result = tripad({ fn: "tripad" })
const pads = result.circuitJson.filter((e) => e.type === "pcb_smtpad")
expect(pads.length).toBe(3)

// Pin 1 is top-left small pad (highest y)
const pin1 = pads.find(
(e) => e.type === "pcb_smtpad" && e.port_hints?.includes("1"),
)
const pin2 = pads.find(
(e) => e.type === "pcb_smtpad" && e.port_hints?.includes("2"),
)
const pin3 = pads.find(
(e) => e.type === "pcb_smtpad" && e.port_hints?.includes("3"),
)

expect(pin1).toBeDefined()
expect(pin2).toBeDefined()
expect(pin3).toBeDefined()

// Pin 1 and 3 are on the left (negative x), pin 2 is on the right (positive x)
if (pin1 && pin2 && pin3) {
expect((pin1 as any).x).toBeLessThan((pin2 as any).x)
expect((pin3 as any).x).toBeLessThan((pin2 as any).x)
// Pin 1 is above pin 3
expect((pin1 as any).y).toBeGreaterThan((pin3 as any).y)
// Pin 2 (large pad) is centered vertically
expect((pin2 as any).y).toBe(0)
// Large pad is bigger than small pads
expect((pin2 as any).width).toBeGreaterThan((pin1 as any).width)
expect((pin2 as any).height).toBeGreaterThan((pin1 as any).height)
}
})
11 changes: 11 additions & 0 deletions tests/tripad4.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { test, expect } from "bun:test"
import { convertCircuitJsonToPcbSvg } from "circuit-to-svg"
import { tripad } from "../src/fn/tripad"

test("tripad direct call with custom params", () => {
const result = tripad({ fn: "tripad", w: "6mm", h: "5mm", p: "3mm" })
const pads = result.circuitJson.filter((e) => e.type === "pcb_smtpad")
expect(pads.length).toBe(3)
const svgContent = convertCircuitJsonToPcbSvg(result.circuitJson)
expect(svgContent).toMatchSvgSnapshot(import.meta.path, "tripad_custom_call")
})
Loading