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
2 changes: 2 additions & 0 deletions src/fn/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export { sot886 } from "./sot886"
export { sot23 } from "./sot23"
export { sot25 } from "./sot25"
export { dfn } from "./dfn"
export { utdfn4ep } from "./utdfn4ep"
export { pinrow } from "./pinrow"
export { sot563 } from "./sot563"
export { ms012 } from "./ms012"
Expand Down Expand Up @@ -48,6 +49,7 @@ export { sod128 } from "./sod128"
export { sot89 } from "./sot89"
export { to220 } from "./to220"
export { to220f } from "./to220f"
export { to220h } from "./to220h"
export { minimelf } from "./minimelf"
export { sod882d } from "./sod882d"
export { melf } from "./melf"
Expand Down
164 changes: 164 additions & 0 deletions src/fn/to220h.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import type { AnyCircuitElement, PcbSilkscreenPath } from "circuit-json"
import { mm } from "@tscircuit/mm"
import { length } from "circuit-json"
import { z } from "zod"
import { platedHoleWithRectPad } from "../helpers/platedHoleWithRectPad"
import { platedHolePill } from "../helpers/platedHolePill"
import { type SilkscreenRef, silkscreenRef } from "../helpers/silkscreenRef"
import { base_def } from "../helpers/zod/base_def"

// TO-220 Horizontal uses 2.54mm standard pitch (matches KiCad)
const TO220H_PITCH_MM = 2.54

export const to220h_def = base_def.extend({
fn: z.string(),
id: length.optional().default("1.1mm"),
od: length.optional().default("1.905mm"),
ph: length.optional().default("2mm"),
num_pins: z.number().optional(),
// tabup / tabdown can be passed as boolean flags (from footprinter string parser)
// or "tab" can be set explicitly
tabup: z.boolean().optional(),
tabdown: z.boolean().optional(),
string: z.string().optional(),
})

export type To220hDef = z.input<typeof to220h_def>

export const to220h = (
raw_params: To220hDef,
): { circuitJson: AnyCircuitElement[]; parameters: any } => {
const parameters = to220h_def.parse(raw_params)

const numPins =
parameters.num_pins ??
Number.parseInt(
parameters.string?.match(/^to220h(?:[_-](\d+))?/i)?.[1] ?? "3",
)

// Determine tab direction: tabup flag takes precedence, default is "down"
const isTabUp =
parameters.tabup === true ||
(parameters.string !== undefined && /tabup/i.test(parameters.string))
const sign = isTabUp ? -1 : 1

// Holes: centered at x=0 with 2.54mm pitch (matches to220f)
const holes: AnyCircuitElement[] = Array.from({ length: numPins }, (_, i) => {
const x =
numPins % 2 === 0
? (i - numPins / 2 + 0.5) * TO220H_PITCH_MM
: (i - Math.floor(numPins / 2)) * TO220H_PITCH_MM

if (i === 0) {
return platedHoleWithRectPad({
pn: 1,
x,
y: 0,
holeDiameter: parameters.id,
rectPadWidth: parameters.od,
rectPadHeight: parameters.ph,
}) as AnyCircuitElement
}

return platedHolePill(
i + 1,
x,
0,
mm(parameters.id),
mm(parameters.od),
mm(parameters.ph),
) as AnyCircuitElement
})

// Body silkscreen dimensions derived from KiCad TO-220-3_Horizontal_TabDown
// KiCad pins at x=0,2.54,5.08 (our pins centered at x=0: -2.54,0,2.54)
// KiCad body x: -2.57 to 7.65 → width = 10.22mm, center at 2.54
// Translating to our center-at-0: x from -5.11 to 5.11
// KiCad body y (TabDown): near=3.7, far=13.17, tab far=19.57
// Lead clearance: 3.7mm; body height: 9.47mm; tab: 6.4mm
const bodyXLeft = -5.11
const bodyXRight = 5.11
const leadClearance = 3.7 - TO220H_PITCH_MM // 1.16mm (pad edge to body)
const bodyH = 9.47
const tabH = 6.4

const bodyNearY = sign * leadClearance
const bodyFarY = sign * (leadClearance + bodyH)
const tabFarY = sign * (leadClearance + bodyH + tabH)

// Body outline
const silkBody: PcbSilkscreenPath = {
type: "pcb_silkscreen_path",
layer: "top",
pcb_component_id: "",
pcb_silkscreen_path_id: "silkscreen_body",
stroke_width: 0.12,
route: [
{ x: bodyXLeft, y: bodyNearY },
{ x: bodyXRight, y: bodyNearY },
{ x: bodyXRight, y: bodyFarY },
{ x: bodyXLeft, y: bodyFarY },
{ x: bodyXLeft, y: bodyNearY },
],
}

// Tab outline
const silkTab: PcbSilkscreenPath = {
type: "pcb_silkscreen_path",
layer: "top",
pcb_component_id: "",
pcb_silkscreen_path_id: "silkscreen_tab",
stroke_width: 0.12,
route: [
{ x: bodyXLeft, y: bodyFarY },
{ x: bodyXRight, y: bodyFarY },
{ x: bodyXRight, y: tabFarY },
{ x: bodyXLeft, y: tabFarY },
{ x: bodyXLeft, y: bodyFarY },
],
}

// Lead lines from body edge toward pin holes
const halfPw = mm(parameters.od) / 2
const leadLines: PcbSilkscreenPath[] = Array.from(
{ length: numPins },
(_, i) => {
const x =
numPins % 2 === 0
? (i - numPins / 2 + 0.5) * TO220H_PITCH_MM
: (i - Math.floor(numPins / 2)) * TO220H_PITCH_MM
return {
type: "pcb_silkscreen_path" as const,
layer: "top" as const,
pcb_component_id: "",
pcb_silkscreen_path_id: `silkscreen_lead_${i + 1}`,
stroke_width: 0.12,
route: [
{ x: x - halfPw, y: bodyNearY },
{ x: x - halfPw, y: sign * halfPw },
],
}
},
)

const silkscreenRefText: SilkscreenRef = silkscreenRef(
0,
isTabUp ? leadClearance + 0.8 : -(leadClearance + 0.8),
0.5,
)

return {
circuitJson: [
...holes,
silkBody,
silkTab,
...leadLines,
silkscreenRefText as AnyCircuitElement,
],
parameters: {
...parameters,
p: TO220H_PITCH_MM,
num_pins: numPins,
},
}
}
101 changes: 101 additions & 0 deletions src/fn/utdfn4ep.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import type { AnyCircuitElement, PcbSilkscreenPath } from "circuit-json"
import { type SilkscreenRef, silkscreenRef } from "src/helpers/silkscreenRef"
import { rectpad } from "src/helpers/rectpad"
import { z } from "zod"
import { base_def } from "../helpers/zod/base_def"

/**
* UTDFN-4-EP (1x1 mm) footprint
*
* Ultra-Thin Dual Flat No-Lead, 4 corner pads + 1 central exposed thermal pad.
*
* References (JLCPCB parts with this footprint):
* - Microchip MIC5366-1.8YMT-TZ https://jlcpcb.com/partdetail/C621364
* - Fitipower FP6182-28X7 https://jlcpcb.com/partdetail/C498349
*
* Land pattern dimensions based on:
* Microchip DS20005619G, page 11 (recommended courtyard 1.4 × 1.3 mm).
*/
export const utdfn4ep_def = base_def.extend({
fn: z.string(),
/** Pad width (mm string, e.g. "0.28mm") */
pw: z.string().default("0.28mm"),
/** Pad height (mm string, e.g. "0.4mm") */
ph: z.string().default("0.4mm"),
/** Exposed-pad width (mm string) */
epw: z.string().default("0.5mm"),
/** Exposed-pad height (mm string) */
eph: z.string().default("0.4mm"),
string: z.string().optional(),
})

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

const pw = Number.parseFloat(parameters.pw)
const ph = Number.parseFloat(parameters.ph)
const epw = Number.parseFloat(parameters.epw)
const eph = Number.parseFloat(parameters.eph)

// Body is 1.0 × 1.0 mm; pads sit at the four corners.
// Pad centres at ±(0.5 - pw/2) in x, ±(0.5 - ph/2) in y.
const cx = 0.5 - pw / 2
const cy = 0.5 - ph / 2

const pads: AnyCircuitElement[] = [
// CCW numbering starting top-left
rectpad(1, -cx, cy, pw, ph),
rectpad(2, cx, cy, pw, ph),
rectpad(3, cx, -cy, pw, ph),
rectpad(4, -cx, -cy, pw, ph),
// Exposed thermal pad (EP)
rectpad(5, 0, 0, epw, eph),
]

// Silkscreen outline: two L-shaped edge marks (top and bottom)
const bx = 0.7
const by = 0.65
const notch = 0.2

const silkscreenTop: PcbSilkscreenPath = {
type: "pcb_silkscreen_path",
layer: "top",
pcb_component_id: "",
pcb_silkscreen_path_id: "silkscreen_top",
route: [
{ x: -bx, y: by - notch },
{ x: -bx, y: by },
{ x: bx, y: by },
{ x: bx, y: by - notch },
],
stroke_width: 0.05,
}

const silkscreenBottom: PcbSilkscreenPath = {
type: "pcb_silkscreen_path",
layer: "top",
pcb_component_id: "",
pcb_silkscreen_path_id: "silkscreen_bottom",
route: [
{ x: -bx, y: -(by - notch) },
{ x: -bx, y: -by },
{ x: bx, y: -by },
{ x: bx, y: -(by - notch) },
],
stroke_width: 0.05,
}

const refText: SilkscreenRef = silkscreenRef(0, by + 0.3, 0.3)

return {
circuitJson: [
...pads,
silkscreenTop,
silkscreenBottom,
refText as AnyCircuitElement,
],
parameters,
}
}
6 changes: 6 additions & 0 deletions src/footprinter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ export type Footprinter = {
ssop: (num_pins?: number) => FootprinterParamsBuilder<"w" | "p">
tssop: (num_pins?: number) => FootprinterParamsBuilder<"w" | "p">
dfn: (num_pins?: number) => FootprinterParamsBuilder<"w" | "p">
utdfn4ep: () => FootprinterParamsBuilder<"pw" | "ph" | "epw" | "eph">
pinrow: (
num_pins?: number,
) => FootprinterParamsBuilder<
Expand Down Expand Up @@ -110,6 +111,7 @@ export type Footprinter = {
hc49: () => FootprinterParamsBuilder<"p" | "id" | "od" | "w" | "h">
to220: () => FootprinterParamsBuilder<"w" | "h" | "p" | "id" | "od">
to220f: () => FootprinterParamsBuilder<"w" | "h" | "p" | "id" | "od">
to220h: () => FootprinterParamsBuilder<"id" | "od" | "ph" | "tab">
sot363: () => FootprinterParamsBuilder<"w" | "h" | "p" | "pl" | "pw">
sot886: () => FootprinterParamsBuilder<"w" | "h" | "p" | "pl" | "pw">
sot457: () => FootprinterParamsBuilder<
Expand Down Expand Up @@ -271,6 +273,10 @@ const normalizeDefinition = (def: string): string => {
.trim()
.replace(/^sot-223-(\d+)(?=_|$)/i, "sot223_$1")
.replace(/^to-220f-(\d+)(?=_|$)/i, "to220f_$1")
.replace(/^to-220-(\d+)[_-]horizontal/i, "to220h_$1")
.replace(/^to-220-horizontal(?:-(\d+))?/i, (_, n) =>
n ? `to220h_${n}` : "to220h",
)
}

export const string = (def: string): Footprinter => {
Expand Down
1 change: 1 addition & 0 deletions tests/__snapshots__/to220h_3.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__/to220h_3_tabup.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
Loading